1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2021-2021. 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-module(observer_sock_wx). 20 21%% {ok, S1} = socket:open(inet, stream, tcp). 22%% {ok, S2} = socket:open(inet6, stream, tcp). 23%% {ok, S3} = socket:open(inet, dgram, udp). 24%% {ok, S4} = socket:open(inet6, dgram, udp). 25%% {ok, S5} = socket:open(inet, seqpacket, sctp). 26%% {ok, S6} = socket:open(inet6, seqpacket, sctp). 27%% {ok, S7} = socket:open(local, stream, default). 28%% {ok, S8} = socket:open(local, dgram, default). 29%% {ok, S9} = gen_tcp:listen(0, [{inet_backend, socket}]). 30%% ok = socket:bind(S7, #{family => local, path => "/tmp/foobarA"}). 31%% ok = socket:bind(S8, #{family => local, path => "/tmp/foobarB"}). 32 33-export([start_link/3]). 34 35%% wx_object callbacks 36-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, 37 handle_event/2, handle_sync_event/3, handle_cast/2]). 38 39-behaviour(wx_object). 40-include_lib("wx/include/wx.hrl"). 41-include("observer_defs.hrl"). 42 43-define(GRID, 300). 44-define(ID_REFRESH, 301). 45-define(ID_REFRESH_INTERVAL, 302). 46-define(ID_SOCKET_INFO, 303). 47-define(ID_SOCKET_INFO_SELECTED, 304). 48%% -define(ID_DEBUG_SOCKETS, 305). 49%% -define(ID_DEBUG_NAMES, 306). 50%% -define(ID_DEBUG_NEW, 307). 51%% -define(ID_DEBUG_ALL, 308). 52-define(ID_CLOSE_SOCKET, 309). 53 54-define(DEBUG_SOCKETS_STR, "Debug selected sockets"). 55 56-record(socket, 57 {id, 58 id_str, 59 kind, 60 fd, 61 owner, 62 domain, 63 type, 64 protocol, 65 raddress, 66 laddress, 67 rstate, 68 wstate, 69 monitored_by, 70 statistics, 71 options}). 72 73-record(opt, {sort_key = 2, 74 sort_incr = true, 75 odd_bg 76 }). 77 78-record(state, 79 { 80 parent, 81 grid, 82 panel, 83 sizer, 84 fields, 85 node = {node(), true}, 86 opt = #opt{}, 87 right_clicked_socket, 88 sockets, 89 timer, 90 open_wins=[] 91 }). 92 93start_link(Notebook, Parent, Config) -> 94 wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). 95 96info_fields() -> 97 Gen = [{"General socket info", 98 [{"IOV Max", iov_max}, 99 {"Counter Size (in bits)", num_cnt_bits}, 100 {"Number of sockets", num_sockets}, 101 {"Number of (socket) monitors", num_monitors}, 102 {"Number of sockets in the 'inet' domain", num_dinet}, 103 {"Number of sockets in the 'inet6' domain", num_dinet6}, 104 {"Number of sockets in the 'local' domain", num_dlocal}, 105 {"Number of type 'stream' sockets", num_tstreams}, 106 {"Number of type 'dgram' sockets", num_tdgrams}, 107 {"Number of type 'seqpacket' sockets", num_tseqpkgs}, 108 {"Number of protocol 'ip' sockets", num_pip}, 109 {"Number of protocol 'sctp' sockets", num_psctp}, 110 {"Number of protocol 'tcp' sockets", num_ptcp}, 111 {"Number of protocol 'udp' sockets", num_pudp} 112 ]}], 113 Gen. 114 115update_gen_socket_info(#state{node = {Node, true}, 116 fields = Fields, 117 sizer = Sizer}) -> 118 case rpc:call(Node, observer_backend, socket_info, []) of 119 Info when is_list(Info) -> 120 Gen = info_fields(), 121 observer_lib:update_info(Fields, 122 observer_lib:fill_info(Gen, Info, 123 "Not Supported")), 124 wxSizer:layout(Sizer); 125 _ -> 126 ignore 127 end; 128update_gen_socket_info(#state{node = _}) -> 129 ignore. 130 131 132%% Two parts of this panel: 133%% 1) First part is general socket info (basically: socket:info/0) 134%% 2) Second part is a list (grid) och each socket and info about it 135init([Notebook, Parent, Config]) -> 136 %% put(debug, true), 137 try 138 begin 139 do_init(Notebook, Parent, Config, observer_backend:socket_info()) 140 end 141 catch 142 _C:_E:_S -> 143 %% Current node does not support socket (windows?) 144 do_init(Notebook, Parent, Config, []) 145 end. 146 147do_init(Notebook, Parent, Config, Info) -> 148 Gen = info_fields(), 149 Panel = wxPanel:new(Notebook), 150 Sizer = wxBoxSizer:new(?wxVERTICAL), 151 GenSizer = wxBoxSizer:new(?wxHORIZONTAL), 152 {GenPanel, _GenSizer, GenFields} = 153 observer_lib:display_info(Panel, 154 observer_lib:fill_info(Gen, Info, 155 "Not Supported")), 156 wxSizer:add(GenSizer, GenPanel, 157 [{flag, ?wxEXPAND}, {proportion, 1}]), 158 BorderFlags = ?wxLEFT bor ?wxRIGHT, 159 wxSizer:add(Sizer, GenSizer, 160 [{flag, ?wxEXPAND bor BorderFlags bor ?wxTOP}, 161 {proportion, 0}, {border, 5}]), 162 Style = ?wxLC_REPORT bor ?wxLC_HRULES, 163 Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style}]), 164 wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, 165 {proportion, 1}, {border, 5}]), 166 wxWindow:setSizer(Panel, Sizer), 167 Li = wxListItem:new(), 168 AddListEntry = fun({Name, Align, DefSize}, Col) -> 169 wxListItem:setText(Li, Name), 170 wxListItem:setAlign(Li, Align), 171 wxListCtrl:insertColumn(Grid, Col, Li), 172 wxListCtrl:setColumnWidth(Grid, Col, DefSize), 173 Col + 1 174 end, 175 Scale = observer_wx:get_scale(), 176 ListItems = [{"Id", ?wxLIST_FORMAT_LEFT, Scale*350}, 177 {"Owner", ?wxLIST_FORMAT_LEFT, Scale*100}, 178 {"Fd", ?wxLIST_FORMAT_LEFT, Scale*50}, 179 {"Domain", ?wxLIST_FORMAT_LEFT, Scale*60}, 180 {"Type", ?wxLIST_FORMAT_LEFT, Scale*100}, 181 {"Protocol", ?wxLIST_FORMAT_LEFT, Scale*100}, 182 {"Read State", ?wxLIST_FORMAT_LEFT, Scale*150}, 183 {"Write State", ?wxLIST_FORMAT_LEFT, Scale*150}], 184 lists:foldl(AddListEntry, 0, ListItems), 185 wxListItem:destroy(Li), 186 187 wxListCtrl:connect(Grid, command_list_item_right_click), 188 wxListCtrl:connect(Grid, command_list_item_activated), 189 wxListCtrl:connect(Grid, command_list_col_click), 190 wxListCtrl:connect(Grid, size, [{skip, true}]), 191 192 wxWindow:setFocus(Grid), 193 Even = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOX), 194 Odd = observer_lib:mix(Even, 195 wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), 196 0.8), 197 Opt = #opt{odd_bg = Odd}, 198 {Panel, #state{parent = Parent, 199 panel = Panel, 200 sizer = Sizer, 201 fields = GenFields, 202 grid = Grid, 203 timer = Config, 204 opt = Opt}}. 205 206handle_event(#wx{id=?ID_REFRESH}, 207 State = #state{node = Node, 208 grid = Grid, 209 opt = Opt} = State) -> 210 _ = update_gen_socket_info(State), 211 Sockets0 = get_sockets(Node), 212 Sockets = update_grid(Grid, sel(State), Opt, Sockets0), 213 {noreply, State#state{sockets = Sockets}}; 214 215handle_event(#wx{obj=Obj, event=#wxClose{}}, #state{open_wins=Opened} = State) -> 216 NewOpened = 217 case lists:keytake(Obj,2,Opened) of 218 false -> Opened; 219 {value,_,Rest} -> Rest 220 end, 221 {noreply, State#state{open_wins=NewOpened}}; 222 223handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, 224 State = #state{node=Node, grid=Grid, 225 opt=Opt0=#opt{sort_key=Key, sort_incr=Bool}}) -> 226 Opt = case Col+2 of 227 Key -> Opt0#opt{sort_incr=not Bool}; 228 NewKey -> Opt0#opt{sort_key=NewKey} 229 end, 230 Sockets0 = get_sockets(Node), 231 Sockets = update_grid(Grid, sel(State), Opt, Sockets0), 232 wxWindow:setFocus(Grid), 233 {noreply, State#state{opt = Opt, sockets = Sockets}}; 234 235handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> 236 observer_lib:set_listctrl_col_size(Grid, W), 237 {noreply, State}; 238 239handle_event(#wx{event = #wxList{type = command_list_item_activated, 240 itemIndex = Index}}, 241 State = #state{grid = Grid, 242 sockets = Sockets, 243 open_wins = Opened}) -> 244 Socket = lists:nth(Index+1, Sockets), 245 NewOpened = display_socket_info(Grid, Socket, Opened), 246 {noreply, State#state{open_wins = NewOpened}}; 247 248handle_event(#wx{event = #wxList{type = command_list_item_right_click, 249 itemIndex = Index}}, 250 State = #state{panel = Panel, sockets=Sockets}) -> 251 case Index of 252 -1 -> 253 {noreply, State}; 254 _ -> 255 Socket = lists:nth(Index + 1, Sockets), 256 Menu = wxMenu:new(), 257 wxMenu:append(Menu, ?ID_SOCKET_INFO, 258 f("Socket info for ~s", [Socket#socket.id_str])), 259 %% wxMenu:append(Menu, ?ID_DEBUG_SOCKETS, 260 %% "Debug selected sockets", 261 %% [{help, ?DEBUG_SOCKETS_STR}]), 262 wxMenu:append(Menu, ?ID_CLOSE_SOCKET, 263 f("Close ~p", [Socket#socket.id_str])), 264 wxWindow:popupMenu(Panel, Menu), 265 wxMenu:destroy(Menu), 266 {noreply, State#state{right_clicked_socket = Socket}} 267 end; 268 269handle_event(#wx{id = ?ID_SOCKET_INFO}, 270 State = #state{grid = Grid, 271 right_clicked_socket = Socket, 272 open_wins = Opened}) -> 273 case Socket of 274 undefined -> 275 {noreply, State}; 276 _ -> 277 NewOpened = display_socket_info(Grid, Socket, Opened), 278 {noreply, State#state{right_clicked_socket = undefined, 279 open_wins = NewOpened}} 280 end; 281 282handle_event(#wx{id = ?ID_SOCKET_INFO_SELECTED}, 283 State = #state{grid = Grid, 284 sockets = Sockets, 285 open_wins = Opened}) -> 286 case get_selected_items(Grid, Sockets) of 287 [] -> 288 observer_wx:create_txt_dialog(State#state.panel, 289 "No selected sockets", 290 "Socket Info", ?wxICON_EXCLAMATION), 291 {noreply, State}; 292 Selected -> 293 NewOpened = lists:foldl(fun(S, O) -> 294 display_socket_info(Grid, S, O) 295 end, 296 Opened, Selected), 297 {noreply, State#state{open_wins = NewOpened}} 298 end; 299 300handle_event(#wx{id = ?ID_CLOSE_SOCKET}, 301 State = #state{right_clicked_socket = Socket}) -> 302 case Socket of 303 undefined -> 304 {noreply, State}; 305 _ -> 306 socket:close(Socket#socket.id), 307 {noreply, State#state{right_clicked_socket = undefined}} 308 end; 309 310%% handle_event(#wx{id=?ID_DEBUG_NEW, event=#wxCommand{type=command_menu_selected}}, State) -> 311%% observer_trace_wx:add_aockets([new_sockets]), 312%% {noreply, State}; 313 314handle_event(#wx{id=?ID_REFRESH_INTERVAL}, 315 State = #state{grid=Grid, timer=Timer0}) -> 316 Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60), 317 {noreply, State#state{timer=Timer}}; 318 319handle_event(#wx{obj=MoreEntry,event=#wxMouse{type=left_down},userData={more,More}}, State) -> 320 observer_lib:add_scroll_entries(MoreEntry,More), 321 {noreply, State}; 322 323handle_event(#wx{event=#wxMouse{type=left_down}, userData=TargetPid}, State) -> 324 observer ! {open_link, TargetPid}, 325 {noreply, State}; 326 327handle_event(#wx{obj=Obj, event=#wxMouse{type=enter_window}}, State) -> 328 wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}), 329 {noreply, State}; 330 331handle_event(#wx{obj=Obj, event=#wxMouse{type=leave_window}}, State) -> 332 wxTextCtrl:setForegroundColour(Obj,?wxBLUE), 333 {noreply, State}; 334 335handle_event(Event, _State) -> 336 error({unhandled_event, Event}). 337 338handle_sync_event(_Event, _Obj, _State) -> 339 ok. 340 341handle_call(get_config, _, #state{timer=Timer}=State) -> 342 {reply, observer_lib:timer_config(Timer), State}; 343 344handle_call(Event, From, _State) -> 345 error({unhandled_call, Event, From}). 346 347handle_cast(Event, _State) -> 348 error({unhandled_cast, Event}). 349 350handle_info(refresh_interval, State = #state{node = Node, 351 grid = Grid, 352 opt = Opt, 353 sockets = OldSockets} = State) -> 354 case get_sockets(Node) of 355 OldSockets -> 356 %% no change 357 {noreply, State}; 358 Sockets0 -> 359 _ = update_gen_socket_info(State), 360 Sockets = update_grid(Grid, sel(State), Opt, Sockets0), 361 {noreply, State#state{sockets = Sockets}} 362 end; 363 364handle_info({active, NodeName}, 365 #state{parent = Parent, 366 grid = Grid, 367 opt = Opt, 368 timer = Timer0} = State0) -> 369 Available = socketinfo_available(NodeName), 370 Available orelse popup_unavailable_info(NodeName), 371 State1 = State0#state{node = {NodeName, Available}}, 372 _ = update_gen_socket_info(State1), 373 Sockets0 = get_sockets(NodeName, Available), 374 Sockets = update_grid(Grid, sel(State1), Opt, Sockets0), 375 wxWindow:setFocus(Grid), 376 create_menus(Parent), 377 Timer = observer_lib:start_timer(Timer0, 10), 378 {noreply, State1#state{sockets = Sockets, 379 timer = Timer}}; 380 381handle_info(not_active, State = #state{timer = Timer0}) -> 382 Timer = observer_lib:stop_timer(Timer0), 383 {noreply, State#state{timer=Timer}}; 384 385handle_info({info, {socket_info_not_available, NodeName}}, 386 State = #state{panel=Panel}) -> 387 Str = io_lib:format("Can not fetch socket info from ~p.~n" 388 "Too old OTP version.", [NodeName]), 389 observer_lib:display_info_dialog(Panel, Str), 390 {noreply, State}; 391 392handle_info({error, Error}, #state{panel=Panel} = State) -> 393 ErrorStr = if is_list(Error) -> Error; 394 true -> f("~p", [Error]) 395 end, 396 Str = io_lib:format("ERROR: ~ts~n", [ErrorStr]), 397 observer_lib:display_info_dialog(Panel, Str), 398 {noreply, State}; 399 400handle_info(_Event, State) -> 401 {noreply, State}. 402 403terminate(_Event, _State) -> 404 ok. 405 406code_change(_, _, State) -> 407 State. 408 409%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 410 411create_menus(Parent) -> 412 MenuEntries = 413 [{"View", 414 [#create_menu{id = ?ID_SOCKET_INFO_SELECTED, 415 text = "Socket info for selected sockets\tCtrl-I"}, 416 separator, 417 #create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, 418 #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval..."} 419 ]}%% , 420 %% {"Debug", 421 %% [#create_menu{id=?ID_DEBUG_SOCKETS, text="Debug selected socket"}, 422 %% #create_menu{id=?ID_DEBUG_NEW, text="Debug new sockets"} 423 %% ]} 424 ], 425 observer_wx:create_menus(Parent, MenuEntries). 426 427 428get_sockets({NodeName, Available}) -> 429 get_sockets(NodeName, Available); 430get_sockets(NodeName) when is_atom(NodeName) -> 431 case rpc:call(NodeName, observer_backend, get_socket_list, []) of 432 SocketInfoMaps when is_list(SocketInfoMaps) -> 433 [infomap_to_rec(SockInfo) || SockInfo <- SocketInfoMaps]; 434 {badrpc, 435 {'EXIT', {undef, [{observer_backend, get_socket_list, [], []}]}}} -> 436 {error, "No socket backend support"}; 437 {badrpc, Error} -> 438 {error, Error}; 439 {error, _} = ERROR -> 440 ERROR 441 end. 442 443get_sockets(_NodeName, false) -> 444 []; 445get_sockets(NodeName, true) -> 446 case get_sockets(NodeName) of 447 {error, _} = ERROR -> 448 self() ! ERROR, 449 []; 450 Res -> 451 Res 452 end. 453 454 455infomap_to_rec(#{id := Id, 456 id_str := IdStr, 457 kind := Kind, 458 fd := FD, 459 owner := Owner, 460 domain := Domain, 461 type := Type, 462 protocol := Protocol, 463 rstates := RState, 464 wstates := WState, 465 monitored_by := MonitoredBy, 466 statistics := Statistics, 467 options := Options} = Info) -> 468 #socket{id = Id, 469 id_str = IdStr, 470 kind = Kind, 471 fd = FD, 472 owner = Owner, 473 domain = Domain, 474 type = Type, 475 protocol = Protocol, 476 raddress = maps:get(raddress, Info, undefined), 477 laddress = maps:get(laddress, Info, undefined), 478 rstate = RState, 479 wstate = WState, 480 monitored_by = MonitoredBy, 481 statistics = Statistics, 482 options = Options}. 483 484socketrec_to_list(#socket{id = Id, 485 id_str = IdStr, 486 kind = Kind, 487 fd = FD, 488 owner = Owner, 489 domain = Domain, 490 type = Type, 491 protocol = Protocol, 492 raddress = RAddr, 493 laddress = LAddr, 494 rstate = RState, 495 wstate = WState, 496 monitored_by = MonitoredBy, 497 statistics = Statistics, 498 options = Options}) -> 499 [{id, Id}, 500 {id_str, IdStr}, 501 {kind, Kind}, 502 {fd, FD}, 503 {owner, Owner}, 504 {domain, Domain}, 505 {type, Type}, 506 {protocol, Protocol}, 507 {raddress, RAddr}, 508 {laddress, LAddr}, 509 {rstate, RState}, 510 {wstate, WState}, 511 {monitored_by, MonitoredBy}, 512 {statistics, Statistics}, 513 {options, Options}]. 514 515display_socket_info(Parent, #socket{id_str = IdStr} = Sock, Opened) -> 516 case lists:keyfind(IdStr, 1, Opened) of 517 false -> 518 Frame = do_display_socket_info(Parent, Sock), 519 [{IdStr, Frame}|Opened]; 520 {_,Win} -> 521 wxFrame:raise(Win), 522 Opened 523 end. 524 525do_display_socket_info(Parent0, #socket{id_str = IdStr} = SocketRec) -> 526 Parent = observer_lib:get_wx_parent(Parent0), 527 Title = "Socket Info: " ++ IdStr, 528 Scale = observer_wx:get_scale(), 529 Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, 530 [{style, ?wxSYSTEM_MENU bor ?wxCAPTION 531 bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}, 532 {size,{Scale * 600, Scale * 400}}]), 533 ScrolledWin = wxScrolledWindow:new(Frame,[{style,?wxHSCROLL bor ?wxVSCROLL}]), 534 wxScrolledWindow:enableScrolling(ScrolledWin,true,true), 535 wxScrolledWindow:setScrollbars(ScrolledWin,20,20,0,0), 536 Sizer = wxBoxSizer:new(?wxVERTICAL), 537 wxWindow:setSizer(ScrolledWin,Sizer), 538 Socket = socketrec_to_list(SocketRec), 539 Fields0 = socket_info_fields(Socket), 540 _UpFields = observer_lib:display_info(ScrolledWin, Sizer, Fields0), 541 wxFrame:center(Frame), 542 wxFrame:connect(Frame, close_window, [{skip, true}]), 543 wxFrame:show(Frame), 544 Frame. 545 546 547 548socket_info_fields(Socket0) -> 549 {Struct0, Socket} = extra_fields(Socket0), 550 Struct = 551 [{"Overview", 552 [{"Owner", {click,owner}}, 553 {"Fd", fd}, 554 {"Domain", domain}, 555 {"Type", type}, 556 {"Protocol", protocol}, 557 {"Read State", rstate}, 558 {"Write State", wstate}]}, 559 {scroll_boxes, 560 [{"Monitored by",1,{click,monitored_by}}]} | Struct0], 561 %% d("socket_info_fields -> " 562 %% "~n Struct: ~p" 563 %% "~n Socket: ~p", [Struct, Socket]), 564 observer_lib:fill_info(Struct, Socket). 565 566extra_fields(Socket) -> 567 Statistics = proplists:get_value(statistics, Socket, []), 568 Options = proplists:get_value(options, Socket, []), 569 Struct = [{"Net", 570 [{"Local Address", laddress}, 571 {"Remote Address", raddress}]}, 572 {"Statistics", 573 [stat_name_and_unit(Key) || {Key,_} <- Statistics]}, 574 {"Options", 575 [{socket, sockopt_to_list(Key), Key} || 576 {Key, _} <- Options]}], 577 Socket1 = lists:keydelete(statistics, 1, Socket), 578 Socket2 = lists:keydelete(options, 1, Socket1), 579 {Struct, Socket2 ++ Statistics ++ Options}. 580 581stat_name_and_unit(acc_fails = Key) -> 582 {"Number of accept fails", Key}; 583stat_name_and_unit(acc_success = Key) -> 584 {"Number of accept success", Key}; 585stat_name_and_unit(acc_tries = Key) -> 586 {"Number of accept tries", Key}; 587stat_name_and_unit(acc_waits = Key) -> 588 {"Number of accept waits", Key}; 589 590stat_name_and_unit(read_byte = Key) -> 591 {"Total read", {bytes, Key}}; 592stat_name_and_unit(read_fails = Key) -> 593 {"Number of read fails", Key}; 594stat_name_and_unit(read_tries = Key) -> 595 {"Number of read tries", Key}; 596stat_name_and_unit(read_waits = Key) -> 597 {"Number of read waits", Key}; 598stat_name_and_unit(read_pkg = Key) -> 599 {"Number of packats read", Key}; 600stat_name_and_unit(read_pkg_max = Key) -> 601 {"Largest package read", {bytes, Key}}; 602 603stat_name_and_unit(write_byte = Key) -> 604 {"Total written", {bytes, Key}}; 605stat_name_and_unit(write_fails = Key) -> 606 {"Number of write fails", Key}; 607stat_name_and_unit(write_tries = Key) -> 608 {"Number of write tries", Key}; 609stat_name_and_unit(write_waits = Key) -> 610 {"Number of write waits", Key}; 611stat_name_and_unit(write_pkg = Key) -> 612 {"Number of packats written", Key}; 613stat_name_and_unit(write_pkg_max = Key) -> 614 {"Largest package written", {bytes, Key}}; 615stat_name_and_unit(Key) -> 616 {atom_to_list(Key), Key}. 617 618sockopt_to_list({LevelOrProto, Opt}) -> 619 f("~w:~w", [LevelOrProto, Opt]). 620 621 622update_grid(Grid, Sel, Opt, Ports) -> 623 wx:batch(fun() -> update_grid2(Grid, Sel, Opt, Ports) end). 624update_grid2(Grid, Sel, #opt{sort_key = Sort, 625 sort_incr = Dir, 626 odd_bg = BG}, Ports) -> 627 wxListCtrl:deleteAllItems(Grid), 628 Update = 629 fun(#socket{id = Id, 630 id_str = IdStr, 631 owner = Owner, 632 fd = Fd, 633 domain = Domain, 634 type = Type, 635 protocol = Proto, 636 rstate = RState, 637 wstate = WState}, 638 Row) -> 639 _Item = wxListCtrl:insertItem(Grid, Row, ""), 640 if (Row rem 2) =:= 1 -> 641 wxListCtrl:setItemBackgroundColour(Grid, Row, BG); 642 true -> ignore 643 end, 644 645 lists:foreach(fun({Col, Val}) -> 646 wxListCtrl:setItem(Grid, Row, Col, 647 observer_lib:to_str(Val)) 648 end, 649 [{0,IdStr}, 650 {1,Owner}, 651 {2,Fd}, 652 {3,Domain}, 653 {4,Type}, 654 {5,Proto}, 655 {6,if (RState =:= []) -> "-"; true -> RState end}, 656 {7,if (WState =:= []) -> "-"; true -> WState end}]), 657 case lists:member(Id, Sel) of 658 true -> 659 wxListCtrl:setItemState(Grid, 660 Row, 661 16#FFFF, 662 ?wxLIST_STATE_SELECTED); 663 false -> 664 wxListCtrl:setItemState(Grid, 665 Row, 666 0, 667 ?wxLIST_STATE_SELECTED) 668 end, 669 Row + 1 670 end, 671 PortInfo = case Dir of 672 false -> lists:reverse(lists:keysort(Sort, Ports)); 673 true -> lists:keysort(Sort, Ports) 674 end, 675 lists:foldl(Update, 0, PortInfo), 676 PortInfo. 677 678sel(#state{grid = Grid, sockets = Sockets}) -> 679 [Id || #socket{id = Id} <- get_selected_items(Grid, Sockets)]. 680 681get_selected_items(Grid, Data) -> 682 get_indecies(get_selected_items(Grid, -1, []), Data). 683get_selected_items(Grid, Index, ItemAcc) -> 684 Item = wxListCtrl:getNextItem(Grid, Index, 685 [{geometry, ?wxLIST_NEXT_ALL}, 686 {state, ?wxLIST_STATE_SELECTED}]), 687 case Item of 688 -1 -> 689 lists:reverse(ItemAcc); 690 _ -> 691 get_selected_items(Grid, Item, [Item | ItemAcc]) 692 end. 693 694get_indecies(Items, Data) -> 695 get_indecies(Items, 0, Data). 696get_indecies([I|Rest], I, [H|T]) -> 697 [H|get_indecies(Rest, I+1, T)]; 698get_indecies(Rest = [_|_], I, [_|T]) -> 699 get_indecies(Rest, I+1, T); 700get_indecies(_, _, _) -> 701 []. 702 703socketinfo_available(NodeName) -> 704 _ = rpc:call(NodeName, code, ensure_loaded, [observer_backend]), 705 case rpc:call(NodeName, erlang, function_exported, 706 [observer_backend, get_socket_list, 0]) of 707 true -> true; 708 false -> false 709 end. 710 711popup_unavailable_info(NodeName) -> 712 self() ! {info, {socket_info_not_available, NodeName}}, 713 ok. 714 715f(F, A) -> 716 lists:flatten(io_lib:format(F, A)). 717 718%% d(F) -> 719%% d(F, []). 720 721%% d(Debug, F) when is_boolean(Debug) andalso is_list(F) -> 722%% d(Debug, F, []); 723%% d(F, A) when is_list(F) andalso is_list(A) -> 724%% d(get(debug), F, A). 725 726%% d(true, F, A) -> 727%% io:format("[oswx] " ++ F ++ "~n", A); 728%% d(_, _, _) -> 729%% ok. 730 731 732