1-module(observer_cli_port).
2
3-include("observer_cli.hrl").
4
5-export([start/2]).
6
7-spec start(pid(), view_opts()) -> no_return.
8start(Port, Opts) ->
9    #view_opts{port = RefreshMs} = Opts,
10    RenderPid = spawn_link(fun() ->
11        ?output(?CLEAR),
12        render_worker(Port, RefreshMs, ?INIT_TIME_REF)
13    end),
14    manager(RenderPid, Opts).
15
16%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
17%%% Private
18%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
19manager(RenderPid, Opts) ->
20    case parse_cmd(Opts, RenderPid) of
21        quit ->
22            erlang:send(RenderPid, quit);
23        {new_interval, NewInterval} ->
24            erlang:send(RenderPid, {new_interval, NewInterval}),
25            manager(RenderPid, Opts#view_opts{port = NewInterval});
26        ViewAction ->
27            erlang:send(RenderPid, ViewAction),
28            manager(RenderPid, Opts)
29    end.
30
31render_worker(Port, Interval, TimeRef) ->
32    PortInfo = recon:port_info(Port),
33    Meta = proplists:get_value(meta, PortInfo),
34    case lists:member(undefined, Meta) of
35        true ->
36            output_die_view(Port, Interval),
37            next_draw_view(TimeRef, Interval, Port);
38        false ->
39            Id = proplists:get_value(id, Meta),
40            Name = proplists:get_value(name, Meta),
41            OsPid = proplists:get_value(os_pid, Meta),
42
43            Signals = proplists:get_value(signals, PortInfo),
44            Link = proplists:get_value(links, Signals),
45            Monitors = proplists:get_value(monitors, Signals),
46            Connected = proplists:get_value(connected, Signals),
47
48            IO = proplists:get_value(io, PortInfo),
49            Input = proplists:get_value(input, IO),
50            Output = proplists:get_value(output, IO),
51
52            MemoryUsed = proplists:get_value(memory_used, PortInfo),
53            Memory = proplists:get_value(memory, MemoryUsed),
54            QueueSize = proplists:get_value(queue_size, MemoryUsed),
55            Menu = render_menu(info, Interval),
56
57            Line1 = render_port_info(
58                Port,
59                Id,
60                Name,
61                OsPid,
62                Input,
63                Output,
64                Memory,
65                QueueSize,
66                Connected
67            ),
68            Line2 = render_link_monitor(Link, Monitors),
69            Line3 = render_type_line(proplists:get_value(type, PortInfo)),
70            LastLine = render_last_line(),
71
72            ?output([?CURSOR_TOP, Menu, Line1, Line2, Line3, LastLine]),
73            next_draw_view(TimeRef, Interval, Port)
74    end.
75
76next_draw_view(TimeRef, Interval, Port) ->
77    NewTimeRef = observer_cli_lib:next_redraw(TimeRef, Interval),
78    next_draw_view_2(NewTimeRef, Interval, Port).
79
80next_draw_view_2(TimeRef, Interval, Port) ->
81    receive
82        quit ->
83            quit;
84        {new_interval, NewInterval} ->
85            ?output(?CLEAR),
86            render_worker(Port, NewInterval, TimeRef);
87        _ ->
88            ?output(?CLEAR),
89            render_worker(Port, Interval, TimeRef)
90    end.
91
92render_port_info(
93    Port,
94    Id,
95    Name,
96    OsPid,
97    Input,
98    Output,
99    Memory,
100    QueueSize,
101    Connected
102) ->
103    QueueSizeColor =
104        case QueueSize > 0 of
105            true -> ?RED;
106            false -> ?GREEN
107        end,
108    Title =
109        ?render([
110            ?GRAY_BG,
111            ?W("Attr", 18),
112            ?W("Value", 20),
113            ?W("Attr", 18),
114            ?W("Value", 20),
115            ?W("Attr", 19),
116            ?W("Value", 21)
117        ]),
118    Rows =
119        ?render([
120            ?W("port", 18),
121            ?W(Port, 20),
122            ?W("id", 18),
123            ?W(Id, 20),
124            ?W("name", 19),
125            ?W(Name, 21),
126            ?NEW_LINE,
127            ?W("queue_size", 18),
128            ?W2(QueueSizeColor, QueueSize, 21),
129            ?W(" input", 19),
130            ?W({byte, Input}, 20),
131            ?W("output", 19),
132            ?W({byte, Output}, 21),
133            ?NEW_LINE,
134            ?W("connected", 18),
135            ?W(Connected, 20),
136            ?W("memory", 18),
137            ?W({byte, Memory}, 20),
138            ?W("os_pid", 19),
139            ?W(OsPid, 21)
140        ]),
141    [Title, Rows].
142
143render_link_monitor(Link, Monitors) ->
144    LinkStr = [
145        begin
146            observer_cli_lib:to_list(P)
147        end
148        || P <- lists:sublist(Link, 30)
149    ],
150    MonitorsStr = [
151        begin
152            case P of
153                {process, Pid} ->
154                    observer_cli_lib:to_list(Pid);
155                {RegName, Node} ->
156                    observer_cli_lib:to_list(RegName) ++ "/" ++ observer_cli_lib:to_list(Node)
157            end
158        end
159        || P <- lists:sublist(Monitors, 30)
160    ],
161    LinkInfo = "Links(" ++ erlang:integer_to_list(erlang:length(Link)) ++ ")",
162    MonitorInfo = "Monitors(" ++ erlang:integer_to_list(erlang:length(Monitors)) ++ ")",
163    ?render([
164        ?W(LinkInfo, 18),
165        ?W(LinkStr, 110),
166        ?NEW_LINE,
167        ?W2(?UNDERLINE, MonitorInfo, 19),
168        ?W2(?UNDERLINE, MonitorsStr, 111)
169    ]).
170
171render_type_line(List) ->
172    PeerName =
173        case lists:keyfind(peername, 1, List) of
174            {_, Peer} -> addr_to_str(Peer);
175            false -> "undefined"
176        end,
177    SockName =
178        case lists:keyfind(sockname, 1, List) of
179            {_, Sock} -> addr_to_str(Sock);
180            false -> "undefined"
181        end,
182    Line1 =
183        ?render([
184            ?UNDERLINE,
185            ?W("            " ++ SockName ++ "(sockname)", 55),
186            ?W("<=============>", 15),
187            ?W("            " ++ PeerName ++ "(peername)", 55)
188        ]),
189    Line2 =
190        case lists:keyfind(statistics, 1, List) of
191            {_, Stats} -> [Line1, render_stats(Stats)];
192            false -> Line1
193        end,
194    case lists:keyfind(options, 1, List) of
195        {_, Opts} -> Line2 ++ [render_opts(Opts)];
196        false -> Line2
197    end.
198
199render_stats(Stats) ->
200    RecvOct = proplists:get_value(recv_oct, Stats),
201    RecvCnt = proplists:get_value(recv_cnt, Stats),
202    RecvMax = proplists:get_value(recv_max, Stats),
203    RecvAvg = proplists:get_value(recv_avg, Stats),
204    RecvDvi = proplists:get_value(recv_dvi, Stats),
205    SendOct = proplists:get_value(send_oct, Stats),
206    SendCnt = proplists:get_value(send_cnt, Stats),
207    SendMax = proplists:get_value(send_max, Stats),
208    SendAvg = proplists:get_value(send_avg, Stats),
209    SendPend = proplists:get_value(send_pend, Stats),
210    ?render([
211        ?W("recv_cnt", 9),
212        ?W(RecvCnt, 12),
213        ?W("recv_oct", 8),
214        ?W({byte, RecvOct}, 12),
215        ?W("recv_max", 9),
216        ?W({byte, RecvMax}, 12),
217        ?W("recv_avg", 9),
218        ?W({byte, RecvAvg}, 12),
219        ?W("recv_dvi", 9),
220        ?W({byte, RecvDvi}, 12),
221        ?NEW_LINE,
222        ?W("send_cnt", 9),
223        ?W(SendCnt, 12),
224        ?W("send_oct", 8),
225        ?W({byte, SendOct}, 12),
226        ?W("send_max", 9),
227        ?W({byte, SendMax}, 12),
228        ?W("send_avg", 9),
229        ?W({byte, SendAvg}, 12),
230        ?W("send_pend", 9),
231        ?W(SendPend, 12)
232    ]).
233
234render_opts(Opts) ->
235    Active = proplists:get_value(active, Opts),
236    Broadcast = proplists:get_value(broadcast, Opts),
237    Buffer = proplists:get_value(buffer, Opts),
238    DelaySend = proplists:get_value(delay_send, Opts),
239    DontRoute = proplists:get_value(dontroute, Opts),
240
241    ExitOnClose = proplists:get_value(exit_on_close, Opts),
242    Header = proplists:get_value(header, Opts),
243    HighWatermark = proplists:get_value(high_watermark, Opts),
244    KeepAlive = proplists:get_value(keepalive, Opts),
245    Linger = io_lib:format("~p", [proplists:get_value(linger, Opts)]),
246
247    LowWatermark = proplists:get_value(low_watermark, Opts),
248    Mode = proplists:get_value(mode, Opts),
249    NoDelay = proplists:get_value(nodelay, Opts),
250    Packet = proplists:get_value(packet, Opts),
251    PacketSize = proplists:get_value(packet_size, Opts),
252
253    Priority = proplists:get_value(priority, Opts),
254    RecBuf = proplists:get_value(recbuf, Opts),
255    ReuseAddr = proplists:get_value(reuseaddr, Opts),
256    SendTimeout = proplists:get_value(send_timeout, Opts),
257    SndBuf = proplists:get_value(sndbuf, Opts),
258    Title =
259        ?render([
260            ?GRAY_BG,
261            ?W("Option", 9),
262            ?W("Value", 6),
263            ?W("Option", 14),
264            ?W("Value", 12),
265            ?W("Option", 9),
266            ?W("Value", 12),
267            ?W("Option", 13),
268            ?W("Value", 8),
269            ?W("Option", 9),
270            ?W("Value", 12)
271        ]),
272    Rows =
273        ?render([
274            ?W("mode", 9),
275            ?W(Mode, 6),
276            ?W("recbuf", 14),
277            ?W({byte, RecBuf}, 12),
278            ?W("sndbuf", 9),
279            ?W({byte, SndBuf}, 12),
280            ?W("delay_send", 13),
281            ?W(DelaySend, 8),
282            ?W("dontroute", 9),
283            ?W(DontRoute, 12),
284            ?NEW_LINE,
285            ?W("reuseaddr", 9),
286            ?W(ReuseAddr, 6),
287            ?W("packet_size", 14),
288            ?W({byte, PacketSize}, 12),
289            ?W("buffer", 9),
290            ?W({byte, Buffer}, 12),
291            ?W("exit_on_close", 13),
292            ?W(ExitOnClose, 8),
293            ?W("priority", 9),
294            ?W(Priority, 12),
295            ?NEW_LINE,
296            ?W("active", 9),
297            ?W(Active, 6),
298            ?W("low_watermark", 14),
299            ?W({byte, LowWatermark}, 12),
300            ?W("header", 9),
301            ?W(Header, 12),
302            ?W("keepalive", 13),
303            ?W(KeepAlive, 8),
304            ?W("linger", 9),
305            ?W(Linger, 12),
306            ?NEW_LINE,
307            ?W("nodelay", 9),
308            ?W(NoDelay, 6),
309            ?W("high_watermark", 14),
310            ?W({byte, HighWatermark}, 12),
311            ?W("broadcast", 9),
312            ?W(Broadcast, 12),
313            ?W("send_timeout", 13),
314            ?W(SendTimeout, 8),
315            ?W("packet", 9),
316            ?W(Packet, 12)
317        ]),
318    [Title, Rows].
319
320render_last_line() ->
321    io_lib:format("|\e[7mq(quit) ~124.124s\e[0m|~n", [" "]).
322
323render_menu(Type, Interval) ->
324    Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
325    Title = get_menu_title(Type),
326    UpTime = observer_cli_lib:uptime(),
327    TitleWidth = ?COLUMN + 41 - erlang:length(UpTime),
328    ?render([?W([Title | Text], TitleWidth) | UpTime]).
329
330get_menu_title(Type) ->
331    [Home, Net, Port] = get_menu_title2(Type),
332    [Home, "|", Net, "|", Port].
333
334get_menu_title2(info) ->
335    [?UNSELECT("Home(H)"), ?UNSELECT("Network(N)"), ?SELECT("Port Info(P)")].
336
337parse_cmd(ViewOpts, Pid) ->
338    case observer_cli_lib:to_list(io:get_line("")) of
339        "q\n" ->
340            quit;
341        "Q\n" ->
342            quit;
343        "P\n" ->
344            info_view;
345        "H\n" ->
346            erlang:exit(Pid, stop),
347            observer_cli:start(ViewOpts);
348        "N\n" ->
349            erlang:exit(Pid, stop),
350            observer_cli_inet:start(ViewOpts);
351        Number ->
352            observer_cli_lib:parse_integer(Number)
353    end.
354
355output_die_view(Port, Interval) ->
356    Menu = render_menu(info, Interval),
357    Line = io_lib:format("\e[31mPort(~p) has already die.\e[0m~n", [Port]),
358    LastLine = render_last_line(),
359    ?output([?CURSOR_TOP, Menu, Line, LastLine]).
360
361addr_to_str({Addr, Port}) ->
362    AddrList = [
363        begin
364            erlang:integer_to_list(A)
365        end
366        || A <- erlang:tuple_to_list(Addr)
367    ],
368    string:join(AddrList, ".") ++ ":" ++ erlang:integer_to_list(Port).
369