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