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