1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2011-2017. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19 20-module(observer_trace_wx). 21 22-export([start_link/3, add_processes/1, add_ports/1]). 23-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, 24 handle_event/2, handle_cast/2]). 25 26-behaviour(wx_object). 27 28-include_lib("wx/include/wx.hrl"). 29-include("observer_defs.hrl"). 30 31-define(SAVE_TRACEOPTS, 305). 32-define(LOAD_TRACEOPTS, 306). 33-define(TOGGLE_TRACE, 307). 34-define(ADD_ALL, 308). 35-define(ADD_NEW_PROCS, 309). 36-define(ADD_NEW_PORTS, 310). 37-define(ADD_TP, 311). 38-define(TRACE_OUTPUT, 312). 39-define(DEF_MS_FUNCS, 313). 40-define(DEF_MS_SEND, 314). 41-define(DEF_MS_RECV, 315). 42-define(DEF_PROC_OPTS, 316). 43-define(DEF_PORT_OPTS, 317). 44 45-define(NODES_WIN, 330). 46-define(ADD_NODES, 331). 47-define(REMOVE_NODES, 332). 48 49-define(PROC_WIN, 340). 50-define(EDIT_PROCS, 341). 51-define(REMOVE_PROCS, 342). 52 53-define(PORT_WIN, 350). 54-define(EDIT_PORTS, 351). 55-define(REMOVE_PORTS, 352). 56 57-define(MODULES_WIN, 360). 58-define(REMOVE_MOD_MS, 361). 59 60-define(FUNCS_WIN, 370). 61-define(EDIT_FUNCS_MS, 371). 62-define(REMOVE_FUNCS_MS, 372). 63 64-define(LOG_WIN, 380). 65-define(LOG_SAVE, 381). 66-define(LOG_CLEAR, 382). 67 68-define(NO_NODES_HELP,"Right click to add nodes"). 69-define(NODES_HELP,"Select nodes to see traced processes and ports"). 70-define(NO_P_HELP,"Add items from Processes/Ports tab"). 71-define(P_HELP,"Select nodes to see traced processes and ports"). 72-define(NO_TP_HELP,"Add trace pattern with button below"). 73-define(TP_HELP,"Select module to see trace patterns"). 74 75-record(state, 76 {parent, 77 panel, 78 n_view, proc_view, port_view, m_view, f_view, %% The listCtrl's 79 logwin, %% The latest log window 80 nodes = [], 81 toggle_button, 82 tpids = [], % #titem 83 tports = [], % #titem 84 def_proc_flags = [], 85 def_port_flags = [], 86 output = [], 87 tpatterns = dict:new(), % Key =:= Module::atom, Value =:= {M, F, A, MatchSpec} 88 match_specs = []}). % [ #match_spec{} ] 89 90-record(titem, {id, opts}). 91 92start_link(Notebook, ParentPid, Config) -> 93 wx_object:start_link(?MODULE, [Notebook, ParentPid, Config], []). 94 95add_processes(Pids) when is_list(Pids) -> 96 wx_object:cast(observer_wx:get_tracer(), {add_processes, Pids}). 97 98add_ports(Ports) when is_list(Ports) -> 99 wx_object:cast(observer_wx:get_tracer(), {add_ports, Ports}). 100 101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 102 103init([Notebook, ParentPid, Config]) -> 104 wx:batch(fun() -> create_window(Notebook, ParentPid, Config) end). 105 106create_window(Notebook, ParentPid, Config) -> 107 %% Create the window 108 Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]), 109 Sizer = wxBoxSizer:new(?wxVERTICAL), 110 Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}, 111 {style, ?SASH_STYLE}]), 112 {NodeProcView, NodeView, ProcessView, PortView} = 113 create_proc_port_view(Splitter), 114 {MatchSpecView,ModView,FuncView} = create_matchspec_view(Splitter), 115 wxSplitterWindow:setSashGravity(Splitter, 0.5), 116 wxSplitterWindow:setMinimumPaneSize(Splitter,50), 117 wxSplitterWindow:splitHorizontally(Splitter, NodeProcView, MatchSpecView, 118 [{sashPosition,368}]), 119 wxSizer:add(Sizer, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}, {proportion, 1}]), 120 %% Buttons 121 Buttons = wxBoxSizer:new(?wxHORIZONTAL), 122 ToggleButton = wxToggleButton:new(Panel, ?TOGGLE_TRACE, "Start Trace", []), 123 wxSizer:add(Buttons, ToggleButton, [{flag, ?wxALIGN_CENTER_VERTICAL}]), 124 wxSizer:addSpacer(Buttons, 15), 125 wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NODES, [{label, "Add Nodes"}])), 126 AllButton = wxButton:new(Panel, ?ADD_ALL, [{label, "Add All"}]), 127 wxControl:setToolTip(AllButton, "Trace all processes and ports"), 128 wxSizer:add(Buttons, AllButton), 129 wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NEW_PROCS, [{label, "Add 'new' Processes"}])), 130 wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NEW_PORTS, [{label, "Add 'new' Ports"}])), 131 wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_TP, [{label, "Add Trace Pattern"}])), 132 wxMenu:connect(Panel, command_togglebutton_clicked, [{skip, true}]), 133 wxMenu:connect(Panel, command_button_clicked, [{skip, true}]), 134 wxSizer:add(Sizer, Buttons, [{flag, ?wxLEFT bor ?wxRIGHT bor ?wxDOWN}, 135 {border, 5}, {proportion,0}]), 136 wxWindow:setSizer(Panel, Sizer), 137 MS = parse_ms(maps:get(match_specs, Config, []), default_matchspecs()), 138 {Panel, #state{parent=ParentPid, panel=Panel, 139 n_view=NodeView, proc_view=ProcessView, port_view=PortView, 140 m_view=ModView, f_view=FuncView, 141 toggle_button = ToggleButton, 142 output=maps:get(output, Config, []), 143 def_proc_flags=maps:get(procflags, Config, []), 144 def_port_flags=maps:get(portflags, Config, []), 145 match_specs=MS 146 }}. 147 148default_matchspecs() -> 149 [{Key,default_matchspecs(Key)} || Key <- [funcs,send,'receive']]. 150default_matchspecs(Key) -> 151 Ms = get_default_matchspecs(Key), 152 [make_ms(Name,Term,FunStr) || {Name,Term,FunStr} <- Ms]. 153 154get_default_matchspecs(funcs) -> 155 [{"Return Trace", [{'_', [], [{return_trace}]}], 156 "fun(_) -> return_trace() end"}, 157 {"Exception Trace", [{'_', [], [{exception_trace}]}], 158 "fun(_) -> exception_trace() end"}, 159 {"Message Caller", [{'_', [], [{message,{caller}}]}], 160 "fun(_) -> message(caller()) end"}, 161 {"Message Dump", [{'_', [], [{message,{process_dump}}]}], 162 "fun(_) -> message(process_dump()) end"}]; 163get_default_matchspecs(send) -> 164 [{"To local node", [{['$1','_'], [{'==',{node,'$1'},{node}}], []}], 165 "fun([Pid,_]) when node(Pid)==node() ->\n true\nend"}, 166 {"To remote node", [{['$1','_'], [{'=/=',{node,'$1'},{node}}], []}], 167 "fun([Pid,_]) when node(Pid)=/=node() ->\n true\nend"}]; 168get_default_matchspecs('receive') -> 169 [{"From local node", [{['$1','_','_'], [{'==','$1',{node}}], []}], 170 "fun([Node,_,_]) when Node==node() ->\n true\nend"}, 171 {"From remote node", [{['$1','_','_'], [{'=/=','$1',{node}}], []}], 172 "fun([Node,_,_]) when Node=/=node() ->\n true\nend"}]. 173 174 175create_proc_port_view(Parent) -> 176 Panel = wxPanel:new(Parent), 177 MainSz = wxBoxSizer:new(?wxHORIZONTAL), 178 Style = ?wxLC_REPORT bor ?wxLC_HRULES, 179 Splitter = wxSplitterWindow:new(Panel, [{style, ?SASH_STYLE}]), 180 Nodes = wxListCtrl:new(Splitter, [{winid, ?NODES_WIN}, {style, Style}]), 181 ProcsPortsSplitter = wxSplitterWindow:new(Splitter, [{style, ?SASH_STYLE}]), 182 Procs = wxListCtrl:new(ProcsPortsSplitter, [{winid,?PROC_WIN},{style,Style}]), 183 Ports = wxListCtrl:new(ProcsPortsSplitter, [{winid,?PORT_WIN},{style,Style}]), 184 Li = wxListItem:new(), 185 wxListItem:setText(Li, "Nodes"), 186 wxListCtrl:insertColumn(Nodes, 0, Li), 187 188 AddProc = fun({Name, Align, DefSize}, Col) -> 189 wxListItem:setText(Li, Name), 190 wxListItem:setAlign(Li, Align), 191 wxListCtrl:insertColumn(Procs, Col, Li), 192 wxListCtrl:setColumnWidth(Procs, Col, DefSize), 193 Col + 1 194 end, 195 Scale = observer_wx:get_scale(), 196 ProcListItems = [{"Process Id", ?wxLIST_FORMAT_CENTER, Scale*120}, 197 {"Trace Options", ?wxLIST_FORMAT_LEFT, Scale*300}], 198 lists:foldl(AddProc, 0, ProcListItems), 199 200 AddPort = fun({Name, Align, DefSize}, Col) -> 201 wxListItem:setText(Li, Name), 202 wxListItem:setAlign(Li, Align), 203 wxListCtrl:insertColumn(Ports, Col, Li), 204 wxListCtrl:setColumnWidth(Ports, Col, DefSize), 205 Col + 1 206 end, 207 PortListItems = [{"Port Id", ?wxLIST_FORMAT_CENTER, Scale*120}, 208 {"Trace Options", ?wxLIST_FORMAT_LEFT, Scale*300}], 209 lists:foldl(AddPort, 0, PortListItems), 210 211 wxListItem:destroy(Li), 212 213 wxSplitterWindow:setSashGravity(Splitter, 0.0), 214 wxSplitterWindow:setMinimumPaneSize(Splitter,50), 215 wxSplitterWindow:splitVertically(Splitter, Nodes, ProcsPortsSplitter, 216 [{sashPosition, 155}]), 217 wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]), 218 219 wxSplitterWindow:setSashGravity(ProcsPortsSplitter, 0.5), 220 wxSplitterWindow:setMinimumPaneSize(ProcsPortsSplitter,50), 221 wxSplitterWindow:splitHorizontally(ProcsPortsSplitter, Procs, Ports, 222 [{sashPosition, 182}]), 223 224 wxListCtrl:connect(Procs, command_list_item_right_click), 225 wxListCtrl:connect(Ports, command_list_item_right_click), 226 wxListCtrl:connect(Nodes, command_list_item_right_click), 227 wxListCtrl:connect(Nodes, command_list_item_selected), 228 wxListCtrl:connect(Procs, size, [{skip, true}]), 229 wxListCtrl:connect(Ports, size, [{skip, true}]), 230 wxListCtrl:connect(Nodes, size, [{skip, true}]), 231 232 wxListCtrl:setToolTip(Nodes, ?NO_NODES_HELP), 233 wxListCtrl:setToolTip(Procs, ?NO_P_HELP), 234 wxListCtrl:setToolTip(Ports, ?NO_P_HELP), 235 236 wxPanel:setSizer(Panel, MainSz), 237 wxWindow:setFocus(Procs), 238 {Panel, Nodes, Procs, Ports}. 239 240create_matchspec_view(Parent) -> 241 Panel = wxPanel:new(Parent), 242 MainSz = wxBoxSizer:new(?wxHORIZONTAL), 243 Style = ?wxLC_REPORT bor ?wxLC_HRULES, 244 Splitter = wxSplitterWindow:new(Panel, [{style, ?SASH_STYLE}]), 245 Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES_WIN}, 246 {style, Style bor ?wxLC_SINGLE_SEL}]), 247 Funcs = wxListCtrl:new(Splitter, [{winid, ?FUNCS_WIN}, {style, Style}]), 248 Li = wxListItem:new(), 249 250 Scale = observer_wx:get_scale(), 251 wxListItem:setText(Li, "Modules"), 252 wxListCtrl:insertColumn(Modules, 0, Li), 253 wxListItem:setText(Li, "Functions"), 254 wxListCtrl:insertColumn(Funcs, 0, Li), 255 wxListCtrl:setColumnWidth(Funcs, 0, Scale*150), 256 wxListItem:setText(Li, "Match Spec"), 257 wxListCtrl:insertColumn(Funcs, 1, Li), 258 wxListCtrl:setColumnWidth(Funcs, 1, Scale*300), 259 wxListItem:destroy(Li), 260 261 wxSplitterWindow:setSashGravity(Splitter, 0.0), 262 wxSplitterWindow:setMinimumPaneSize(Splitter,50), 263 wxSplitterWindow:splitVertically(Splitter, Modules, Funcs, [{sashPosition, 155}]), 264 wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]), 265 266 wxListCtrl:connect(Modules, size, [{skip, true}]), 267 wxListCtrl:connect(Funcs, size, [{skip, true}]), 268 wxListCtrl:connect(Modules, command_list_item_selected), 269 wxListCtrl:connect(Modules, command_list_item_right_click), 270 wxListCtrl:connect(Funcs, command_list_item_right_click), 271 wxListCtrl:setToolTip(Panel, ?NO_TP_HELP), 272 wxPanel:setSizer(Panel, MainSz), 273 {Panel, Modules, Funcs}. 274 275create_menues(Parent) -> 276 Menus = [{"File", 277 [#create_menu{id = ?LOAD_TRACEOPTS, text = "Load settings"}, 278 #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"}]}, 279 {"Options", 280 [#create_menu{id = ?TRACE_OUTPUT, text = "Output"}, 281 #create_menu{id = ?DEF_MS_FUNCS, text = "Default Match Specifications for Functions"}, 282 #create_menu{id = ?DEF_MS_SEND, text = "Default Match Specifications for 'send'"}, 283 #create_menu{id = ?DEF_MS_RECV, text = "Default Match Specifications for 'receive'"}, 284 #create_menu{id = ?DEF_PROC_OPTS, text = "Default Process Options"}, 285 #create_menu{id = ?DEF_PORT_OPTS, text = "Default Port Options"}]} 286 ], 287 observer_wx:create_menus(Parent, Menus). 288 289%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 290%%Main window 291handle_event(#wx{obj=Obj, event=#wxSize{size={W,_}}}, State) -> 292 case wx:getObjectType(Obj) =:= wxListCtrl of 293 true -> observer_lib:set_listctrl_col_size(Obj, W); 294 false -> ok 295 end, 296 {noreply, State}; 297 298handle_event(#wx{id=?ADD_ALL}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) -> 299 try 300 Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts), 301 Process = #titem{id=all, opts=Opts}, 302 {noreply, do_add_processes([Process], State#state{def_proc_flags=Opts})} 303 catch cancel -> {noreply, State} 304 end; 305 306handle_event(#wx{id=?ADD_NEW_PROCS}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) -> 307 try 308 Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts), 309 Process = #titem{id=new_processes, opts=Opts}, 310 {noreply, do_add_processes([Process], State#state{def_proc_flags=Opts})} 311 catch cancel -> {noreply, State} 312 end; 313 314handle_event(#wx{id=?ADD_NEW_PORTS}, State = #state{panel=Parent, def_port_flags=TraceOpts}) -> 315 try 316 Opts = observer_traceoptions_wx:port_trace(Parent, TraceOpts), 317 Port = #titem{id=new_ports, opts=Opts}, 318 {noreply, do_add_ports([Port], State#state{def_port_flags=Opts})} 319 catch cancel -> {noreply, State} 320 end; 321 322handle_event(#wx{id=?ADD_TP}, 323 State = #state{panel=Parent, nodes=Nodes, match_specs=Ms}) -> 324 Node = case Nodes of 325 [N|_] -> N; 326 [] -> node() 327 end, 328 case observer_traceoptions_wx:trace_pattern(self(), Parent, Node, Ms) of 329 cancel -> 330 {noreply, State}; 331 Patterns -> 332 {noreply, do_add_patterns(Patterns, State)} 333 end; 334 335handle_event(#wx{id=?MODULES_WIN, event=#wxList{type=command_list_item_selected, itemIndex=Row}}, 336 State = #state{tpatterns=TPs, m_view=Mview, f_view=Fview}) -> 337 Module = list_to_atom(wxListCtrl:getItemText(Mview, Row)), 338 update_functions_view(dict:fetch(Module, TPs), Fview), 339 {noreply, State}; 340 341handle_event(#wx{id=?NODES_WIN, 342 event=#wxList{type=command_list_item_selected}}, 343 State = #state{tpids=Tpids, tports=Tports, n_view=Nview, 344 proc_view=ProcView, port_view=PortView, nodes=Ns}) -> 345 Nodes = get_selected_items(Nview, Ns), 346 update_p_view(Tpids, ProcView, Nodes), 347 update_p_view(Tports, PortView, Nodes), 348 {noreply, State}; 349 350handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}}, 351 #state{panel = Panel, 352 nodes = Nodes, 353 tpids = TProcs, 354 tports = TPorts, 355 tpatterns = TPs0, 356 toggle_button = ToggleBtn, 357 output = Opts 358 } = State) -> 359 try 360 TPs = dict:to_list(TPs0), 361 (TProcs == []) andalso (TPorts == []) andalso throw({error, "No processes or ports traced"}), 362 (Nodes == []) andalso throw({error, "No nodes traced"}), 363 HaveCallTrace = fun(#titem{opts=Os}) -> lists:member(functions,Os) end, 364 WStr = "Call trace actived but no trace patterns used", 365 (TPs == []) andalso lists:any(HaveCallTrace, TProcs) andalso 366 observer_wx:create_txt_dialog(Panel, WStr, "Warning", ?wxICON_WARNING), 367 368 {TTB, LogWin} = ttb_output_args(Panel, Opts), 369 {ok, _} = ttb:tracer(Nodes, TTB), 370 setup_ttb(TPs, TProcs, TPorts), 371 wxToggleButton:setLabel(ToggleBtn, "Stop Trace"), 372 {noreply, State#state{logwin=LogWin}} 373 catch {error, Msg} -> 374 observer_wx:create_txt_dialog(Panel, Msg, "Error", ?wxICON_ERROR), 375 wxToggleButton:setValue(ToggleBtn, false), 376 {noreply, State} 377 end; 378 379handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 0}}, 380 #state{toggle_button = ToggleBtn} = State) -> 381 %%Stop tracing 382 ttb:stop(nofetch), 383 wxToggleButton:setLabel(ToggleBtn, "Start Trace"), 384 wxToggleButton:setValue(ToggleBtn, false), 385 {noreply, State#state{logwin=false}}; 386 387handle_event(#wx{id=Id, obj=LogWin, event=Ev}, 388 #state{toggle_button = ToggleBtn, logwin=Latest} = State) 389 when Id =:= ?LOG_WIN; is_record(Ev, wxClose) -> 390 case LogWin of 391 Latest -> 392 %%Stop tracing 393 ttb:stop(nofetch), 394 wxToggleButton:setLabel(ToggleBtn, "Start Trace"), 395 wxToggleButton:setValue(ToggleBtn, false), 396 {noreply, State#state{logwin=false}}; 397 _ -> 398 {noreply, State} 399 end; 400 401handle_event(#wx{id=?LOG_CLEAR, userData=TCtrl}, State) -> 402 wxTextCtrl:clear(TCtrl), 403 {noreply, State}; 404 405handle_event(#wx{id=?LOG_SAVE, userData=TCtrl}, #state{panel=Panel} = State) -> 406 Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), 407 case wxFileDialog:showModal(Dialog) of 408 ?wxID_OK -> 409 Path = wxFileDialog:getPath(Dialog), 410 wxDialog:destroy(Dialog), 411 wxTextCtrl:saveFile(TCtrl, [{file, Path}]); 412 _ -> 413 wxDialog:destroy(Dialog), 414 ok 415 end, 416 {noreply, State}; 417 418handle_event(#wx{id = ?SAVE_TRACEOPTS}, 419 #state{panel = Panel} = State) -> 420 Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), 421 case wxFileDialog:showModal(Dialog) of 422 ?wxID_OK -> 423 Path = wxFileDialog:getPath(Dialog), 424 write_file(Panel, Path, get_config(State)); 425 _ -> 426 ok 427 end, 428 wxDialog:destroy(Dialog), 429 {noreply, State}; 430 431 432handle_event(#wx{id = ?LOAD_TRACEOPTS}, #state{panel = Panel} = State) -> 433 Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_FILE_MUST_EXIST}]), 434 State2 = case wxFileDialog:showModal(Dialog) of 435 ?wxID_OK -> 436 Path = wxFileDialog:getPath(Dialog), 437 read_settings(Path, State); 438 _ -> 439 State 440 end, 441 wxDialog:destroy(Dialog), 442 {noreply, State2}; 443 444handle_event(#wx{id=?PROC_WIN, event=#wxList{type=command_list_item_right_click}}, 445 State = #state{panel=Panel, proc_view=LCtrl, tpids=Tpids, 446 n_view=Nview, nodes=Nodes}) -> 447 case get_visible_ps(Tpids, Nodes, Nview) of 448 [] -> 449 ok; 450 Visible -> 451 case get_selected_items(LCtrl, Visible) of 452 [] -> 453 ok; 454 _ -> 455 create_right_click_menu( 456 Panel, 457 [{?EDIT_PROCS, "Edit process options"}, 458 {?REMOVE_PROCS, "Remove processes"}]) 459 end 460 end, 461 {noreply, State}; 462 463handle_event(#wx{id=?PORT_WIN, event=#wxList{type=command_list_item_right_click}}, 464 State = #state{panel=Panel, port_view=LCtrl, tports=Tports, 465 n_view=Nview, nodes=Nodes}) -> 466 case get_visible_ps(Tports, Nodes, Nview) of 467 [] -> 468 ok; 469 Visible -> 470 case get_selected_items(LCtrl, Visible) of 471 [] -> 472 ok; 473 _ -> 474 create_right_click_menu( 475 Panel, 476 [{?EDIT_PORTS, "Edit port options"}, 477 {?REMOVE_PORTS, "Remove ports"}]) 478 end 479 end, 480 {noreply, State}; 481 482handle_event(#wx{id=?MODULES_WIN,event=#wxList{type=command_list_item_right_click}}, 483 State = #state{panel=Panel, m_view=Mview, tpatterns=TPs}) -> 484 case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of 485 [] -> 486 ok; 487 _ -> 488 create_right_click_menu( 489 Panel, 490 [{?REMOVE_MOD_MS, "Remove trace patterns"}]) 491 end, 492 {noreply,State}; 493 494handle_event(#wx{id=?FUNCS_WIN,event=#wxList{type=command_list_item_right_click}}, 495 State = #state{panel=Panel, m_view=Mview, f_view=Fview, 496 tpatterns=TPs}) -> 497 case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of 498 [] -> 499 ok; 500 [Module] -> 501 case get_selected_items(Fview, dict:fetch(Module, TPs)) of 502 [] -> 503 ok; 504 _ -> 505 create_right_click_menu( 506 Panel, 507 [{?EDIT_FUNCS_MS, "Edit matchspecs"}, 508 {?REMOVE_FUNCS_MS, "Remove trace patterns"}]) 509 end 510 end, 511 {noreply,State}; 512 513handle_event(#wx{id=?NODES_WIN,event=#wxList{type=command_list_item_right_click}}, 514 State = #state{panel=Panel, n_view=Nview, nodes=Nodes}) -> 515 Menu = 516 case get_selected_items(Nview, Nodes) of 517 [] -> 518 [{?ADD_NODES, "Add nodes"}]; 519 _ -> 520 [{?ADD_NODES, "Add nodes"}, 521 {?REMOVE_NODES, "Remove nodes"}] 522 end, 523 create_right_click_menu(Panel,Menu), 524 {noreply, State}; 525 526handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, proc_view=Procs} = State) -> 527 try 528 [#titem{opts=DefOpts}|_] = Selected = get_selected_items(Procs, Tpids), 529 Opts = observer_traceoptions_wx:process_trace(Panel, DefOpts), 530 Changed = [Tpid#titem{opts=Opts} || Tpid <- Selected], 531 {noreply, do_add_processes(Changed, State#state{def_proc_flags=Opts})} 532 catch _:_ -> 533 {noreply, State} 534 end; 535 536handle_event(#wx{id=?REMOVE_PROCS}, 537 #state{tpids=Tpids, proc_view=LCtrl, 538 n_view=Nview, nodes=Nodes} = State) -> 539 Selected = get_selected_items(LCtrl, Tpids), 540 Pids = Tpids -- Selected, 541 update_p_view(Pids, LCtrl, Nodes, Nview), 542 {noreply, State#state{tpids=Pids}}; 543 544handle_event(#wx{id=?EDIT_PORTS}, #state{panel=Panel, tports=Tports, port_view=Ports} = State) -> 545 try 546 [#titem{opts=DefOpts}|_] = Selected = get_selected_items(Ports, Tports), 547 Opts = observer_traceoptions_wx:port_trace(Panel, DefOpts), 548 Changed = [Tport#titem{opts=Opts} || Tport <- Selected], 549 {noreply, do_add_ports(Changed, State#state{def_port_flags=Opts})} 550 catch _:_ -> 551 {noreply, State} 552 end; 553 554handle_event(#wx{id=?REMOVE_PORTS}, 555 #state{tports=Tports, port_view=LCtrl, 556 n_view=Nview, nodes=Nodes} = State) -> 557 Selected = get_selected_items(LCtrl, Tports), 558 Ports = Tports -- Selected, 559 update_p_view(Ports, LCtrl, Nodes, Nview), 560 {noreply, State#state{tports=Ports}}; 561 562handle_event(#wx{id=?DEF_PROC_OPTS}, #state{panel=Panel, def_proc_flags=PO} = State) -> 563 try 564 Opts = observer_traceoptions_wx:process_trace(Panel, PO), 565 {noreply, State#state{def_proc_flags=Opts}} 566 catch _:_ -> 567 {noreply, State} 568 end; 569 570handle_event(#wx{id=?DEF_PORT_OPTS}, #state{panel=Panel, def_port_flags=PO} = State) -> 571 try 572 Opts = observer_traceoptions_wx:port_trace(Panel, PO), 573 {noreply, State#state{def_port_flags=Opts}} 574 catch _:_ -> 575 {noreply, State} 576 end; 577 578handle_event(#wx{id=?DEF_MS_FUNCS}, #state{panel=Panel, match_specs=Ms} = State) -> 579 try %% Return selected MS and sends new MS's to us 580 observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, funcs) 581 catch _:_ -> 582 cancel 583 end, 584 {noreply, State}; 585 586handle_event(#wx{id=?DEF_MS_SEND}, #state{panel=Panel, match_specs=Ms} = State) -> 587 try %% Return selected MS and sends new MS's to us 588 observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, send) 589 catch _:_ -> 590 cancel 591 end, 592 {noreply, State}; 593 594handle_event(#wx{id=?DEF_MS_RECV}, #state{panel=Panel, match_specs=Ms} = State) -> 595 try %% Return selected MS and sends new MS's to us 596 observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, 'receive') 597 catch _:_ -> 598 cancel 599 end, 600 {noreply, State}; 601 602handle_event(#wx{id=?EDIT_FUNCS_MS}, #state{panel=Panel, tpatterns=TPs, 603 f_view=LCtrl, m_view=Mview, 604 match_specs=Mss 605 } = State) -> 606 try 607 case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of 608 [] -> 609 throw({error,"No module selected"}); 610 [Module] -> 611 Selected = get_selected_items(LCtrl, dict:fetch(Module, TPs)), 612 Key = case Module of 613 'Events' -> 614 SelectedEvents = 615 [Event || #tpattern{fa=Event} <- Selected], 616 E1 = hd(SelectedEvents), 617 case lists:all(fun(E) when E==E1 -> true; 618 (_) -> false 619 end, 620 SelectedEvents) of 621 true -> E1; 622 false -> throw({error,"Can not set match specs for multiple event types"}) 623 end; 624 _ -> funcs 625 end, 626 Ms = observer_traceoptions_wx:select_matchspec(self(), Panel, 627 Mss, Key), 628 Changed = [TP#tpattern{ms=Ms} || TP <- Selected], 629 {noreply, do_add_patterns({Module, Changed}, State)} 630 end 631 catch {error, Msg} -> 632 observer_wx:create_txt_dialog(Panel, Msg, "Error", ?wxICON_ERROR), 633 {noreply, State}; 634 cancel -> 635 {noreply, State} 636 end; 637 638handle_event(#wx{id=?REMOVE_FUNCS_MS}, #state{tpatterns=TPs0, f_view=LCtrl, m_view=Mview} = State) -> 639 case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs0))) of 640 [] -> {noreply, State}; 641 [Module] -> 642 FMs0 = dict:fetch(Module, TPs0), 643 Selected = get_selected_items(LCtrl, FMs0), 644 FMs = FMs0 -- Selected, 645 update_functions_view(FMs, LCtrl), 646 TPs = case FMs of 647 [] -> 648 New = dict:erase(Module, TPs0), 649 update_modules_view(lists:sort(dict:fetch_keys(New)), Module, Mview), 650 New; 651 _ -> 652 dict:store(Module, FMs, TPs0) 653 end, 654 {noreply, State#state{tpatterns=TPs}} 655 end; 656 657handle_event(#wx{id=?REMOVE_MOD_MS}, #state{tpatterns=TPs0, f_view=LCtrl, m_view=Mview} = State) -> 658 case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs0))) of 659 [] -> {noreply, State}; 660 [Module] -> 661 update_functions_view([], LCtrl), 662 TPs = dict:erase(Module, TPs0), 663 update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview), 664 {noreply, State#state{tpatterns=TPs}} 665 end; 666 667handle_event(#wx{id=?TRACE_OUTPUT}, #state{panel=Panel, output=Out0} = State) -> 668 try 669 Out = observer_traceoptions_wx:output(Panel, Out0), 670 {noreply, State#state{output=Out}} 671 catch _:_ -> 672 {noreply, State} 673 end; 674 675handle_event(#wx{id=?ADD_NODES}, #state{panel=Panel, n_view=Nview, nodes=Ns0} = State) -> 676 try 677 Possible = [node()|nodes()] -- Ns0, 678 case Possible of 679 [] -> 680 Msg = "Already selected all connected nodes\n" 681 "Use the Nodes menu to connect to new nodes first.", 682 observer_wx:create_txt_dialog(Panel, Msg, "No available nodes", ?wxICON_INFORMATION), 683 throw(cancel); 684 _ -> 685 Ns = lists:usort(Ns0 ++ observer_traceoptions_wx:select_nodes(Panel, Possible)), 686 update_nodes_view(Ns, Nview), 687 {noreply, State#state{nodes=Ns}} 688 end 689 catch cancel -> 690 {noreply, State} 691 end; 692 693handle_event(#wx{id=?REMOVE_NODES}, #state{n_view=Nview, nodes=Ns0} = State) -> 694 Sel = get_selected_items(Nview, Ns0), 695 Ns = Ns0 -- Sel, 696 update_nodes_view(Ns, Nview), 697 {noreply, State#state{nodes = Ns}}; 698 699handle_event(#wx{id=ID, event = What}, State) -> 700 io:format("~p:~p: Unhandled event: ~p, ~tp ~n", [?MODULE, ?LINE, ID, What]), 701 {noreply, State}. 702 703%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 704handle_call(get_config, _, State) -> 705 Config0 = get_config(State), 706 Config = lists:keydelete(trace_p, 1, Config0), 707 {reply, maps:from_list(Config), State}; 708handle_call(Msg, From, _State) -> 709 error({unhandled_call, Msg, From}). 710 711%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 712handle_cast({add_processes, Pids}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) -> 713 try 714 Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts), 715 POpts = [#titem{id=Pid, opts=Opts} || Pid <- Pids], 716 S = do_add_processes(POpts, State#state{def_proc_flags=Opts}), 717 {noreply, S} 718 catch cancel -> 719 {noreply, State} 720 end; 721handle_cast({add_ports, Ports}, State = #state{panel=Parent, def_port_flags=TraceOpts}) -> 722 try 723 Opts = observer_traceoptions_wx:port_trace(Parent, TraceOpts), 724 POpts = [#titem{id=Id, opts=Opts} || Id <- Ports], 725 S = do_add_ports(POpts, State#state{def_port_flags=Opts}), 726 {noreply, S} 727 catch cancel -> 728 {noreply, State} 729 end; 730handle_cast(Msg, _State) -> 731 error({unhandled_cast, Msg}). 732 733%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 734 735handle_info({active, _Node}, State=#state{parent=Parent}) -> 736 create_menues(Parent), 737 {noreply, State}; 738 739handle_info(not_active, State) -> 740 {noreply, State}; 741 742handle_info({update_ms, NewMs}, State) -> 743 {noreply, State#state{match_specs=NewMs}}; 744 745handle_info(Any, State) -> 746 io:format("~p~p: received unexpected message: ~tp\n", [?MODULE, self(), Any]), 747 {noreply, State}. 748 749terminate(_Reason, #state{nodes=_Nodes}) -> 750 ttb:stop(nofetch), 751 ok. 752 753code_change(_, _, State) -> 754 {ok, State}. 755 756%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 757do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_view=Fview}) -> 758 Old = case dict:find(Module, TPs0) of 759 {ok, Prev} -> Prev; 760 error -> [] 761 end, 762 case merge_patterns(NewPs, Old) of 763 {Old, [], []} -> 764 State; 765 {MPatterns, _New, _Changed} -> 766 %% if dynamicly updates update New and Changed 767 TPs = dict:store(Module, MPatterns, TPs0), 768 update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview), 769 update_functions_view(dict:fetch(Module, TPs), Fview), 770 State#state{tpatterns=TPs} 771 end. 772 773do_add_processes(POpts, S0=#state{n_view=Nview, proc_view=LCtrl, tpids=OldPids, nodes=OldNodes}) -> 774 CheckFun = fun(Pid) -> is_pid(Pid) end, 775 {Pids, Nodes} = do_add_pid_or_port(POpts, Nview, LCtrl, 776 OldPids, OldNodes, CheckFun), 777 S0#state{tpids=Pids, nodes=Nodes}. 778 779do_add_ports(POpts, S0=#state{n_view=Nview, port_view=LCtrl, tports=OldPorts, nodes=OldNodes}) -> 780 CheckFun = fun(Port) -> is_port(Port) end, 781 {Ports, Nodes} = do_add_pid_or_port(POpts, Nview, LCtrl, 782 OldPorts, OldNodes, CheckFun), 783 S0#state{tports=Ports, nodes=Nodes}. 784 785do_add_pid_or_port(POpts, Nview, LCtrl, OldPs, Ns0, Check) -> 786 case merge_trace_items(POpts, OldPs) of 787 {OldPs, [], []} -> 788 {OldPs,Ns0}; 789 {Ps, New, _Changed} -> 790 Ns1 = lists:usort([node(Id) || #titem{id=Id} <- New, Check(Id)]), 791 Nodes = case ordsets:subtract(Ns1, Ns0) of 792 [] when Ns0==[] -> [observer_wx:get_active_node()]; 793 [] -> Ns0; %% No new Nodes 794 NewNs -> ordsets:union(NewNs, Ns0) 795 end, 796 update_nodes_view(Nodes, Nview), 797 update_p_view(Ps, LCtrl, Nodes, Nview), 798 {Ps, Nodes} 799 end. 800 801%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 802get_visible_ps(PidsOrPorts, [Node], _Nview) -> 803 %% If only one node, treat this as selected 804 get_visible_ps(PidsOrPorts, [Node]); 805get_visible_ps(PidsOrPorts, Nodes, Nview) -> 806 get_visible_ps(PidsOrPorts, get_selected_items(Nview, Nodes)). 807 808get_visible_ps(PidsOrPorts, Nodes) -> 809 %% Show pids/ports belonging to the selected nodes only (+ named pids/ports) 810 [P || P <- PidsOrPorts, 811 is_atom(P#titem.id) orelse 812 lists:member(node(P#titem.id),Nodes)]. 813 814update_p_view(PidsOrPorts, LCtrl, Nodes, Nview) -> 815 update_p_view(get_visible_ps(PidsOrPorts, Nodes, Nview), LCtrl). 816update_p_view(PidsOrPorts, LCtrl, Nodes) -> 817 update_p_view(get_visible_ps(PidsOrPorts, Nodes), LCtrl). 818 819update_p_view(PidsOrPorts, LCtrl) -> 820 %% pid- or port-view 821 wxListCtrl:deleteAllItems(LCtrl), 822 wx:foldl(fun(#titem{id=Id, opts=Opts}, Row) -> 823 _Item = wxListCtrl:insertItem(LCtrl, Row, ""), 824 ?EVEN(Row) andalso 825 wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), 826 wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Id)), 827 wxListCtrl:setItem(LCtrl, Row, 1, observer_lib:to_str(Opts)), 828 Row+1 829 end, 0, PidsOrPorts), 830 case PidsOrPorts of 831 [] -> 832 wxListCtrl:setToolTip(LCtrl,?NO_P_HELP); 833 _ -> 834 wxListCtrl:setToolTip(LCtrl,?P_HELP) 835 end. 836 837update_nodes_view(Nodes, LCtrl) -> 838 Selected = 839 case Nodes of 840 [_] -> Nodes; 841 _ -> get_selected_items(LCtrl, Nodes) 842 end, 843 wxListCtrl:deleteAllItems(LCtrl), 844 wx:foldl(fun(Node, Row) -> 845 _Item = wxListCtrl:insertItem(LCtrl, Row, ""), 846 ?EVEN(Row) andalso 847 wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), 848 wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Node)), 849 lists:member(Node,Selected) andalso % keep selection 850 wxListCtrl:setItemState(LCtrl, Row, 16#FFFF, 851 ?wxLIST_STATE_SELECTED), 852 Row+1 853 end, 0, Nodes), 854 case Nodes of 855 [] -> 856 wxListCtrl:setToolTip(LCtrl,?NO_NODES_HELP); 857 _ -> 858 wxListCtrl:setToolTip(LCtrl,?NODES_HELP) 859 end. 860 861update_modules_view(Mods, Module, LCtrl) -> 862 wxListCtrl:deleteAllItems(LCtrl), 863 wx:foldl(fun(Mod, Row) -> 864 _Item = wxListCtrl:insertItem(LCtrl, Row, ""), 865 ?EVEN(Row) andalso 866 wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), 867 wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Mod)), 868 (Mod =:= Module) andalso 869 wxListCtrl:setItemState(LCtrl, Row, 16#FFFF, ?wxLIST_STATE_SELECTED), 870 Row+1 871 end, 0, Mods), 872 Parent = wxListCtrl:getParent(LCtrl), 873 case Mods of 874 [] -> 875 wxListCtrl:setToolTip(Parent,?NO_TP_HELP); 876 _ -> 877 wxListCtrl:setToolTip(Parent,?TP_HELP) 878 end. 879 880update_functions_view(Funcs, LCtrl) -> 881 wxListCtrl:deleteAllItems(LCtrl), 882 wx:foldl(fun(#tpattern{m=M, fa=FA, ms=#match_spec{str=Ms}}, Row) -> 883 _Item = wxListCtrl:insertItem(LCtrl, Row, ""), 884 ?EVEN(Row) andalso wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), 885 FuncStr = 886 case M of 887 'Events' -> 888 observer_lib:to_str(FA); 889 _ -> 890 observer_lib:to_str({func,FA}) 891 end, 892 wxListCtrl:setItem(LCtrl, Row, 0, FuncStr), 893 wxListCtrl:setItem(LCtrl, Row, 1, Ms), 894 Row+1 895 end, 0, Funcs). 896 897%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 898%% Trace items are processes and ports 899merge_trace_items([N1=#titem{id=NewP}|Ns], [N2=#titem{id=NewP}|Old]) 900 when NewP==new_processes; NewP==new_ports; NewP==all -> 901 {Ids, New, Changed} = merge_trace_items_1(Ns,Old), 902 {[N1|Ids], New, [{N2,N2}|Changed]}; 903merge_trace_items([N1=#titem{id=NewP}|Ns], Old) 904 when NewP==new_processes; NewP==new_ports; NewP==all -> 905 {Ids, New, Changed} = merge_trace_items_1(Ns,Old), 906 {[N1|Ids], [N1|New], Changed}; 907merge_trace_items(Ns, [N2=#titem{id=NewP}|Old]) 908 when NewP==new_processes; NewP==new_ports; NewP==all -> 909 {Ids, New, Changed} = merge_trace_items_1(Ns,Old), 910 {[N2|Ids], New, Changed}; 911merge_trace_items(New, Old) -> 912 merge_trace_items_1(New, Old). 913 914merge_trace_items_1(New, Old) -> 915 merge(lists:sort(New), Old, #titem.id, [], [], []). 916 917merge_patterns(New, Old) -> 918 merge(lists:sort(New), Old, #tpattern.fa, [], [], []). 919 920merge([N|Ns], [N|Os], El, New, Ch, All) -> 921 merge(Ns, Os, El, New, Ch, [N|All]); 922merge([N|Ns], [O|Os], El, New, Ch, All) 923 when element(El, N) == element(El, O) -> 924 merge(Ns, Os, El, New, [{O,N}|Ch], [N|All]); 925merge([N|Ns], Os=[O|_], El, New, Ch, All) 926 when element(El, N) < element(El, O) -> 927 merge(Ns, Os, El, [N|New], Ch, [N|All]); 928merge(Ns=[N|_], [O|Os], El, New, Ch, All) 929 when element(El, N) > element(El, O) -> 930 merge(Ns, Os, El, New, Ch, [O|All]); 931merge([], Os, _El, New, Ch, All) -> 932 {lists:reverse(All, Os), New, Ch}; 933merge(Ns, [], _El, New, Ch, All) -> 934 {lists:reverse(All, Ns), Ns++New, Ch}. 935 936%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 937ttb_output_args(Parent, Opts) -> 938 ToWindow = proplists:get_value(window, Opts, true), 939 ToShell = proplists:get_value(shell, Opts, false), 940 ToFile = proplists:get_value(file, Opts, false), 941 ToWindow orelse ToShell orelse ToFile orelse 942 throw({error, "No output of trace"}), 943 {LogWin,Text} = create_logwindow(Parent, ToWindow), 944 Write = output_fun(Text, ToShell), 945 Shell = output_shell(ToFile, Write), 946 FileOpts = output_file(ToFile, proplists:get_value(wrap, Opts, false), Opts), 947 {[{file, {local,FileOpts}}|Shell], LogWin}. 948 949output_shell(true, false) -> 950 []; %% File only 951output_shell(true, Write) when is_function(Write) -> 952 [{shell, Write}]; 953output_shell(false, Write) when is_function(Write) -> 954 [{shell, {only, Write}}]. 955 956output_fun(false, false) -> false; 957output_fun(false, true) -> fun(Trace) -> io:put_chars(textformat(Trace)) end; 958output_fun(Text, false) -> 959 Env = wx:get_env(), 960 fun(Trace) -> 961 wx:set_env(Env), 962 wxTextCtrl:appendText(Text, textformat(Trace)) 963 end; 964output_fun(Text, true) -> 965 Env = wx:get_env(), 966 fun(Trace) -> 967 wx:set_env(Env), 968 IoList = textformat(Trace), 969 wxTextCtrl:appendText(Text, IoList), 970 io:put_chars(textformat(Trace)) 971 end. 972 973output_file(false, _, _Opts) -> 974 "ttb"; %% Will be ignored 975output_file(true, false, Opts) -> 976 proplists:get_value(filename, Opts, "ttb"); 977output_file(true, true, Opts) -> 978 Name = proplists:get_value(filename, Opts, "ttb"), 979 Size = proplists:get_value(wrap_sz, Opts, 128), 980 Count = proplists:get_value(wrap_c, Opts, 8), 981 {wrap, Name, Size*1024, Count}. 982 983 984create_logwindow(_Parent, false) -> {false, false}; 985create_logwindow(Parent, true) -> 986 Scale = observer_wx:get_scale(), 987 LogWin = wxFrame:new(Parent, ?LOG_WIN, "Trace Log", [{size, {750*Scale, 800*Scale}}]), 988 MB = wxMenuBar:new(), 989 File = wxMenu:new(), 990 wxMenu:append(File, ?LOG_CLEAR, "Clear Log\tCtrl-C"), 991 wxMenu:append(File, ?LOG_SAVE, "Save Log\tCtrl-S"), 992 wxMenu:append(File, ?wxID_CLOSE, "Close"), 993 wxMenuBar:append(MB, File, "File"), 994 wxFrame:setMenuBar(LogWin, MB), 995 Text = wxTextCtrl:new(LogWin, ?wxID_ANY, 996 [{style, ?wxTE_MULTILINE bor ?wxTE_RICH2 bor 997 ?wxTE_DONTWRAP bor ?wxTE_READONLY}]), 998 Font = observer_wx:get_attrib({font, fixed}), 999 Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]), 1000 true = wxTextCtrl:setDefaultStyle(Text, Attr), 1001 wxFrame:connect(LogWin, close_window, [{skip, true}]), 1002 wxFrame:connect(LogWin, command_menu_selected, [{userData, Text}]), 1003 wxFrame:show(LogWin), 1004 {LogWin, Text}. 1005 1006setup_ttb(TPs, TPids, TPorts) -> 1007 _R1 = [setup_tps(FTP, []) || {_, FTP} <- TPs], 1008 _R2 = [ttb:p(Pid, dbg_flags(proc,Flags)) || 1009 #titem{id=Pid, opts=Flags} <- TPids], 1010 _R3 = [ttb:p(Port, dbg_flags(port,Flags)) || 1011 #titem{id=Port, opts=Flags} <- TPorts], 1012 ok. 1013 1014%% Sigh order is important 1015setup_tps([First=#tpattern{fa={_,'_'}}|Rest], Prev) -> 1016 setup_tp(First), 1017 [setup_tp(TP) || TP <- lists:reverse(Prev)], 1018 setup_tps(Rest, []); 1019setup_tps([First=#tpattern{fa={F,_}}|Rest], Prev = [#tpattern{fa={F,_}}|_]) -> 1020 setup_tps(Rest, [First|Prev]); 1021setup_tps([First|Rest], Prev) -> 1022 [setup_tp(TP) || TP <- lists:reverse(Prev)], 1023 setup_tps(Rest, [First]); 1024setup_tps([], Prev) -> 1025 [setup_tp(TP) || TP <- lists:reverse(Prev)]. 1026 1027setup_tp(#tpattern{m='Events',fa=Event, ms=#match_spec{term=Ms}}) -> 1028 ttb:tpe(Event,Ms); 1029setup_tp(#tpattern{m=M,fa={F,A}, ms=#match_spec{term=Ms}}) -> 1030 ttb:tpl(M,F,A,Ms). 1031 1032dbg_flags(Type,Flags) -> 1033 [dbg_flag(Type,Flag) || Flag <- Flags]. 1034 1035dbg_flag(_,send) -> s; 1036dbg_flag(_,'receive') -> r; 1037dbg_flag(proc,functions) -> c; 1038dbg_flag(proc,on_spawn) -> sos; 1039dbg_flag(proc,on_link) -> sol; 1040dbg_flag(proc,on_first_spawn) -> sofs; 1041dbg_flag(proc,on_first_link) -> sofl; 1042dbg_flag(proc,events) -> p; 1043dbg_flag(port,events) -> ports; 1044dbg_flag(_,Flag) -> Flag. 1045 1046textformat(Trace) when element(1, Trace) == trace_ts, tuple_size(Trace) >= 4 -> 1047 format_trace(Trace, tuple_size(Trace)-1, element(tuple_size(Trace),Trace)); 1048textformat(Trace) when element(1, Trace) == drop, tuple_size(Trace) =:= 2 -> 1049 io_lib:format("*** Dropped ~p messages.~n", [element(2,Trace)]); 1050textformat(Trace) when element(1, Trace) == seq_trace, tuple_size(Trace) >= 3 -> 1051 io_lib:format("*** Seq trace not implmented.~n", []); 1052textformat(_) -> 1053 "". 1054 1055format_trace(Trace, Size, TS0={_,_,MS}) -> 1056 {_,{H,M,S}} = calendar:now_to_local_time(TS0), 1057 TS = io_lib:format("~.2.0w:~.2.0w:~.2.0w:~.6.0w", [H,M,S,MS]), 1058 From = element(2, Trace), 1059 case element(3, Trace) of 1060 'receive' -> 1061 case element(4, Trace) of 1062 {dbg,ok} -> ""; 1063 Message -> 1064 io_lib:format("~s (~100p) << ~100tp~n", [TS,From,Message]) 1065 end; 1066 'send' -> 1067 Message = element(4, Trace), 1068 To = element(5, Trace), 1069 io_lib:format("~s (~100p) ~100p ! ~100tp~n", [TS,From,To,Message]); 1070 call -> 1071 case element(4, Trace) of 1072 MFA when Size == 5 -> 1073 Message = element(5, Trace), 1074 io_lib:format("~s (~100p) call ~ts (~100tp) ~n", [TS,From,ffunc(MFA),Message]); 1075 MFA -> 1076 io_lib:format("~s (~100p) call ~ts~n", [TS,From,ffunc(MFA)]) 1077 end; 1078 return_from -> 1079 MFA = element(4, Trace), 1080 Ret = element(5, Trace), 1081 io_lib:format("~s (~100p) returned from ~ts -> ~100tp~n", [TS,From,ffunc(MFA),Ret]); 1082 return_to -> 1083 MFA = element(4, Trace), 1084 io_lib:format("~s (~100p) returning to ~ts~n", [TS,From,ffunc(MFA)]); 1085 spawn when Size == 5 -> 1086 Pid = element(4, Trace), 1087 MFA = element(5, Trace), 1088 io_lib:format("~s (~100p) spawn ~100p as ~ts~n", [TS,From,Pid,ffunc(MFA)]); 1089 Op -> 1090 io_lib:format("~s (~100p) ~100p ~ts~n", [TS,From,Op,ftup(Trace,4,Size)]) 1091 end. 1092 1093%%% These f* functions returns non-flat strings 1094 1095%% {M,F,[A1, A2, ..., AN]} -> "M:F(A1, A2, ..., AN)" 1096%% {M,F,A} -> "M:F/A" 1097ffunc({M,F,Argl}) when is_list(Argl) -> 1098 io_lib:format("~100p:~100tp(~ts)", [M, F, fargs(Argl)]); 1099ffunc({M,F,Arity}) -> 1100 io_lib:format("~100p:~100tp/~100p", [M,F,Arity]); 1101ffunc(X) -> io_lib:format("~100tp", [X]). 1102 1103%% Integer -> "Integer" 1104%% [A1, A2, ..., AN] -> "A1, A2, ..., AN" 1105fargs(Arity) when is_integer(Arity) -> integer_to_list(Arity); 1106fargs([]) -> []; 1107fargs([A]) -> io_lib:format("~100tp", [A]); %% last arg 1108fargs([A|Args]) -> [io_lib:format("~100tp,", [A]) | fargs(Args)]; 1109fargs(A) -> io_lib:format("~100tp", [A]). % last or only arg 1110 1111%% {A_1, A_2, ..., A_N} -> "A_Index A_Index+1 ... A_Size" 1112ftup(Trace, Index, Index) -> 1113 io_lib:format("~100tp", [element(Index, Trace)]); 1114ftup(Trace, Index, Size) -> 1115 [io_lib:format("~100tp ", [element(Index, Trace)]) 1116 | ftup(Trace, Index+1, Size)]. 1117 1118%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1119 1120get_config(#state{def_proc_flags = ProcFlags, 1121 def_port_flags = PortFlags, 1122 match_specs = MatchSpecs0, 1123 tpatterns = TracePatterns, 1124 output = Output}) -> 1125 MSToList = fun(#match_spec{name=Id, term=T, func=F}) -> 1126 [{name,Id},{term,T},{func,F}] 1127 end, 1128 MatchSpecs = [{ms,Key,[MSToList(MS) || MS <- MSs]} || 1129 {Key,MSs} <- MatchSpecs0], 1130 TPToTuple = fun(#tpattern{fa={F,A}, ms=Ms}) -> 1131 {F,A,MSToList(Ms)} 1132 end, 1133 ModuleTermList = [{tp, Module, [TPToTuple(FTP) || FTP <- FTPs]} || 1134 {Module,FTPs} <- dict:to_list(TracePatterns)], 1135 [{procflags,ProcFlags}, 1136 {portflags,PortFlags}, 1137 {match_specs,MatchSpecs}, 1138 {output,Output}, 1139 {trace_p,ModuleTermList}]. 1140 1141write_file(Frame, Filename, Config) -> 1142 Str = 1143 ["%%% ",epp:encoding_to_string(utf8), "\n" 1144 "%%%\n%%% This file is generated by Observer\n", 1145 "%%%\n%%% DO NOT EDIT!\n%%%\n", 1146 [io_lib:format("~tp.~n",[MSTerm]) || 1147 MSTerm <- proplists:get_value(match_specs, Config)], 1148 io_lib:format("~p.~n",[lists:keyfind(procflags, 1, Config)]), 1149 io_lib:format("~p.~n",[lists:keyfind(portflags, 1, Config)]), 1150 io_lib:format("~tp.~n",[lists:keyfind(output, 1, Config)]), 1151 [io_lib:format("~tp.~n",[ModuleTerm]) || 1152 ModuleTerm <- proplists:get_value(trace_p, Config)] 1153 ], 1154 1155 case file:write_file(Filename, unicode:characters_to_binary(Str)) of 1156 ok -> 1157 success; 1158 {error, Reason} -> 1159 FailMsg = file:format_error(Reason), 1160 observer_wx:create_txt_dialog(Frame, FailMsg, "Error", ?wxICON_ERROR) 1161 end. 1162 1163read_settings(Filename, #state{match_specs=Ms0, def_proc_flags=ProcFs0, def_port_flags=PortFs0} = State) -> 1164 case file:consult(Filename) of 1165 {ok, Terms} -> 1166 Ms = parse_ms(Terms, Ms0), 1167 ProcFs1 = proplists:get_value(procflags, Terms, []) ++ 1168 proplists:get_value(traceopts, Terms, []), % for backwards comp. 1169 ProcFs = lists:usort(ProcFs0 ++ ProcFs1), 1170 PortFs = lists:usort(PortFs0 ++ 1171 proplists:get_value(portflags, Terms, [])), 1172 Out = proplists:get_value(output, Terms, []), 1173 lists:foldl(fun parse_tp/2, 1174 State#state{match_specs=Ms, def_proc_flags=ProcFs, 1175 def_port_flags=PortFs, output=Out}, 1176 Terms); 1177 {error, _} -> 1178 observer_wx:create_txt_dialog(State#state.panel, 1179 "Could not load settings", 1180 "Error", ?wxICON_ERROR), 1181 State 1182 end. 1183 1184parse_ms(Terms, OldMSs) -> 1185 MSs = 1186 case [{Key,[make_ms(MS) || MS <- MSs]} || {ms,Key,MSs} <- Terms] of 1187 [] -> 1188 case [make_ms(MS) || {ms,MS} <- Terms] of 1189 [] -> 1190 []; 1191 FuncMSs -> % for backwards compatibility 1192 [{funcs,FuncMSs}] 1193 end; 1194 KeyMSs -> 1195 KeyMSs 1196 end, 1197 parse_ms_1(MSs, dict:from_list(OldMSs)). 1198 1199parse_ms_1([{Key,MSs} | T], Dict) -> 1200 parse_ms_1(T, dict:append_list(Key,MSs,Dict)); 1201parse_ms_1([],Dict) -> 1202 [{Key,rm_dups(MSs,[])} || {Key,MSs} <- dict:to_list(Dict)]. 1203 1204rm_dups([H|T],Acc) -> 1205 case lists:member(H,Acc) of 1206 true -> 1207 rm_dups(T,Acc); 1208 false -> 1209 rm_dups(T,[H|Acc]) 1210 end; 1211rm_dups([],Acc) -> 1212 lists:reverse(Acc). 1213 1214make_ms(MS) -> 1215 [{func,FunStr},{name,Name},{term,Term}] = lists:keysort(1,MS), 1216 make_ms(Name,Term,FunStr). 1217 1218make_ms(Name, Term, FunStr) -> 1219 #match_spec{name=Name, term=Term, str=io_lib:format("~tw", [Term]), func = FunStr}. 1220 1221parse_tp({tp, Mod, FAs}, State) -> 1222 Patterns = [#tpattern{m=Mod,fa={F,A}, ms=make_ms(List)} || 1223 {F,A,List} <- FAs], 1224 do_add_patterns({Mod, Patterns}, State); 1225parse_tp(_, State) -> 1226 State. 1227 1228get_selected_items(Grid, Data) -> 1229 get_indecies(get_selected_items(Grid, -1, []), Data). 1230get_selected_items(Grid, Index, ItemAcc) -> 1231 Item = wxListCtrl:getNextItem(Grid, Index, [{geometry, ?wxLIST_NEXT_ALL}, 1232 {state, ?wxLIST_STATE_SELECTED}]), 1233 case Item of 1234 -1 -> 1235 lists:reverse(ItemAcc); 1236 _ -> 1237 get_selected_items(Grid, Item, [Item | ItemAcc]) 1238 end. 1239 1240get_indecies(Items, Data) -> 1241 get_indecies(Items, 0, Data). 1242get_indecies([I|Rest], I, [H|T]) -> 1243 [H|get_indecies(Rest, I+1, T)]; 1244get_indecies(Rest = [_|_], I, [_|T]) -> 1245 get_indecies(Rest, I+1, T); 1246get_indecies(_, _, _) -> 1247 []. 1248 1249create_right_click_menu(Panel,Menus) -> 1250 Menu = wxMenu:new(), 1251 [wxMenu:append(Menu,Id,Str) || {Id,Str} <- Menus], 1252 wxWindow:popupMenu(Panel, Menu), 1253 wxMenu:destroy(Menu). 1254