1%%% @author zhongwen <zhongwencool@gmail.com>
2-module(observer_cli_inet).
3
4-include("observer_cli.hrl").
5
6%% API
7-export([start/1]).
8-export([clean/1]).
9
10-define(LAST_LINE,
11    "q(quit) ic(inet_count) iw(inet_window) rc(recv_cnt) ro(recv_oct)"
12    " sc(send_cnt) so(send_oct) cnt oct 9(port 9 info) pd/pu(page:down/up)"
13).
14
15-spec start(view_opts()) -> no_return.
16start(#view_opts{inet = InetOpt, auto_row = AutoRow} = ViewOpts) ->
17    StorePid = observer_cli_store:start(),
18    RenderPid = spawn_link(
19        fun() ->
20            ?output(?CLEAR),
21            {{input, In}, {output, Out}} = erlang:statistics(io),
22            render_worker(StorePid, InetOpt, ?INIT_TIME_REF, 0, {In, Out}, AutoRow)
23        end
24    ),
25    manager(StorePid, RenderPid, ViewOpts).
26
27-spec clean(list()) -> ok.
28clean(Pids) -> observer_cli_lib:exit_processes(Pids).
29
30%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
31%%% Private
32%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33manager(StorePid, RenderPid, ViewOpts = #view_opts{inet = InetOpts}) ->
34    #inet{cur_page = CurPage, pages = Pages} = InetOpts,
35    case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [RenderPid, StorePid]) of
36        quit ->
37            observer_cli_lib:exit_processes([StorePid]),
38            erlang:send(RenderPid, quit);
39        {new_interval, NewInterval} ->
40            erlang:send(RenderPid, {new_interval, NewInterval}),
41            NewInet = InetOpts#inet{interval = NewInterval},
42            manager(StorePid, RenderPid, ViewOpts#view_opts{inet = NewInet});
43        Func when Func =:= inet_count; Func =:= inet_window ->
44            clean([StorePid, RenderPid]),
45            start(ViewOpts#view_opts{inet = InetOpts#inet{func = Func}});
46        Type when
47            Type =:= recv_cnt;
48            Type =:= recv_oct;
49            Type =:= send_cnt;
50            Type =:= send_oct;
51            Type =:= cnt;
52            Type =:= oct
53        ->
54            clean([StorePid, RenderPid]),
55            start(ViewOpts#view_opts{inet = InetOpts#inet{type = Type}});
56        {jump, NewPos} ->
57            NewPages = observer_cli_lib:update_page_pos(CurPage, NewPos, Pages),
58            NewInetOpts = InetOpts#inet{pages = NewPages},
59            start_port_view(StorePid, RenderPid, ViewOpts#view_opts{inet = NewInetOpts}, false);
60        jump ->
61            start_port_view(StorePid, RenderPid, ViewOpts, true);
62        page_down_top_n ->
63            NewPage = max(CurPage + 1, 1),
64            NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages),
65            clean([StorePid, RenderPid]),
66            start(ViewOpts#view_opts{inet = InetOpts#inet{cur_page = NewPage, pages = NewPages}});
67        page_up_top_n ->
68            NewPage = max(CurPage - 1, 1),
69            NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages),
70            clean([StorePid, RenderPid]),
71            start(ViewOpts#view_opts{inet = InetOpts#inet{cur_page = NewPage, pages = NewPages}});
72        _ ->
73            manager(StorePid, RenderPid, ViewOpts)
74    end.
75
76render_worker(StorePid, InetOpt, LastTimeRef, Count, LastIO, AutoRow) ->
77    #inet{func = Function, type = Type, interval = Interval, cur_page = CurPage} = InetOpt,
78    TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
79    Row = erlang:max(TerminalRow - 5, 0),
80    Text = get_menu_str(Function, Type, Interval, Row),
81    Menu = observer_cli_lib:render_menu(inet, Text),
82    TopLen = Row * CurPage,
83    InetList = inet_info(Function, Type, TopLen, Interval, Count),
84    {IORows, NewIO} = render_io_rows(LastIO),
85    {PortList, InetRows} = render_inet_rows(InetList, TopLen, InetOpt),
86    LastLine = observer_cli_lib:render_last_line(?LAST_LINE),
87    ?output([?CURSOR_TOP, Menu, IORows, InetRows, LastLine]),
88    observer_cli_store:update(StorePid, Row, PortList),
89    NewInterval =
90        case Function of
91            inet_count -> Interval;
92            inet_window -> 10
93        end,
94    TimeRef = observer_cli_lib:next_redraw(LastTimeRef, NewInterval),
95    receive
96        {new_interval, NewInterval} ->
97            ?output(?CLEAR),
98            render_worker(
99                StorePid,
100                InetOpt#inet{interval = NewInterval},
101                TimeRef,
102                Count + 1,
103                NewIO,
104                AutoRow
105            );
106        quit ->
107            quit;
108        _ ->
109            render_worker(StorePid, InetOpt, TimeRef, Count + 1, NewIO, AutoRow)
110    end.
111
112render_io_rows({LastIn, LastOut}) ->
113    {{input, In}, {output, Out}} = erlang:statistics(io),
114    {
115        ?render([
116            ?YELLOW,
117            ?W("Byte Input", 14),
118            ?W({byte, In - LastIn}, 12),
119            ?W("Byte Output", 13),
120            ?W({byte, Out - LastOut}, 12),
121            ?W("Total Input", 15),
122            ?W({byte, In}, 17),
123            ?W("Total Output", 15),
124            ?W({byte, Out}, 17)
125        ]),
126        {In, Out}
127    }.
128
129render_inet_rows([], Rows, #inet{func = inet_count, type = Type}) ->
130    {[], io_lib:format("\e[32;1mGet nothing for recon:inet_count(~p, ~p)\e[0m~n", [Type, Rows])};
131render_inet_rows([], Rows, #inet{func = inet_window, type = Type, interval = Interval}) ->
132    {[],
133        io_lib:format("\e[32;1mGet nothing for recon:inet_window(~p, ~p, ~p)\e[0m~n", [
134            Type,
135            Rows,
136            Interval
137        ])};
138render_inet_rows(InetList, Num, #inet{
139    type = Type,
140    pages = Pages,
141    cur_page = Page
142}) when Type =:= cnt orelse Type =:= oct ->
143    {Unit, RecvType, SendType} = trans_type(Type),
144    Title = title(Type, RecvType, SendType),
145    {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(InetList)),
146    FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
147        {Port, Value, [{_, Recv}, {_, Send}]} = Item,
148        {memory_used, MemoryUsed} = recon:port_info(Port, memory_used),
149        {io, IO} = recon:port_info(Port, io),
150        Input = proplists:get_value(input, IO),
151        Output = proplists:get_value(output, IO),
152        QueueSize = proplists:get_value(queue_size, MemoryUsed),
153        Memory = proplists:get_value(memory, MemoryUsed),
154        IP = get_remote_ip(Port),
155        {ValueFormat, RecvFormat, SendFormat} = trans_format(Unit, Value, Recv, Send),
156        R = [
157            ?W(Pos, 2),
158            ?W(Port, 16),
159            ValueFormat,
160            RecvFormat,
161            SendFormat,
162            ?W({byte, Output}, 12),
163            ?W({byte, Input}, 12),
164            ?W(QueueSize, 6),
165            ?W({byte, Memory}, 12),
166            ?W(IP, 19)
167        ],
168        Rows = add_choose_color(ChoosePos, Pos, R),
169        {[?render(Rows) | Acc], [{Pos, Port} | Acc1], Pos + 1}
170    end,
171    {Rows, PortList, _} = lists:foldl(
172        FormatFunc,
173        {[], [], Start},
174        lists:sublist(InetList, Start, Num)
175    ),
176    {PortList, [Title | lists:reverse(Rows)]};
177render_inet_rows(InetList, Num, #inet{type = Type, pages = Pages, cur_page = Page}) ->
178    {Unit, Type1, Type2} = trans_type(Type),
179    Title = title(Type, Type1, Type2),
180    {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(InetList)),
181    FormatFunc = fun(Item, {Acc, Acc1, Pos}) ->
182        {Port, Value, _} = Item,
183        {memory_used, MemoryUsed} = recon:port_info(Port, memory_used),
184        {io, IO} = recon:port_info(Port, io),
185        Input = proplists:get_value(input, IO),
186        Output = proplists:get_value(output, IO),
187        QueueSize = proplists:get_value(queue_size, MemoryUsed),
188        Memory = proplists:get_value(memory, MemoryUsed),
189        IP = get_remote_ip(Port),
190        Packet1 = getstat(Port, erlang:list_to_existing_atom(Type1)),
191        AllPacket =
192            case is_integer(Packet1) of
193                true -> Value + Packet1;
194                false -> Value
195            end,
196        {ValueFormat, Packet1Format, AllFormat} = trans_format(Unit, Value, Packet1, AllPacket),
197        R = [
198            ?W(Pos, 2),
199            ?W(Port, 16),
200            ValueFormat,
201            Packet1Format,
202            AllFormat,
203            ?W({byte, Input}, 12),
204            ?W({byte, Output}, 12),
205            ?W(QueueSize, 6),
206            ?W({byte, Memory}, 12),
207            ?W(IP, 19)
208        ],
209        Rows = add_choose_color(ChoosePos, Pos, R),
210        {[?render(Rows) | Acc], [{Pos, Port} | Acc1], Pos + 1}
211    end,
212    {Rows, PortList, _} = lists:foldl(
213        FormatFunc,
214        {[], [], Start},
215        lists:sublist(InetList, Start, Num)
216    ),
217    {PortList, [Title | lists:reverse(Rows)]}.
218
219get_menu_str(inet_count, Type, Interval, Rows) ->
220    io_lib:format("recon:inet_count(~p, ~w) Interval:~wms", [Type, Rows, Interval]);
221get_menu_str(inet_window, Type, Interval, Rows) ->
222    io_lib:format("recon:inet_window(~p, ~w, ~w) Interval:~wms", [Type, Rows, Interval, Interval]).
223
224inet_info(inet_count, Type, Num, _, _) -> recon:inet_count(Type, Num);
225inet_info(inet_window, Type, Num, _, 0) -> recon:inet_count(Type, Num);
226inet_info(inet_window, Type, Num, Ms, _) -> recon:inet_window(Type, Num, Ms).
227
228getstat(Port, Attr) ->
229    case inet:getstat(Port, [Attr]) of
230        {ok, [{_, Value}]} -> Value;
231        {error, Err} -> inet:format_error(Err)
232    end.
233
234start_port_view(StorePid, RenderPid, Opts = #view_opts{inet = InetOpt}, AutoJump) ->
235    #inet{cur_page = CurPage, pages = Pages} = InetOpt,
236    {_, CurPos} = lists:keyfind(CurPage, 1, Pages),
237    case observer_cli_store:lookup_pos(StorePid, CurPos) of
238        {CurPos, ChoosePort} ->
239            clean([StorePid, RenderPid]),
240            observer_cli_port:start(ChoosePort, Opts);
241        {_, ChoosePort} when AutoJump ->
242            clean([StorePid, RenderPid]),
243            observer_cli_port:start(ChoosePort, Opts);
244        _ ->
245            manager(StorePid, RenderPid, Opts)
246    end.
247
248trans_type(cnt) ->
249    {number, "recv_cnt", "send_cnt"};
250trans_type(oct) ->
251    {byte, "recv_oct", "send_oct"};
252trans_type(send_cnt) ->
253    {number, "recv_cnt", "cnt"};
254trans_type(recv_cnt) ->
255    {number, "send_cnt", "cnt"};
256trans_type(send_oct) ->
257    {byte, "recv_oct", "oct"};
258trans_type(recv_oct) ->
259    {byte, "send_oct", "oct"}.
260
261trans_format(byte, Val, Val1, Val2) ->
262    {
263        ?W({byte, Val}, 10),
264        ?W({byte, Val1}, 10),
265        ?W({byte, Val2}, 10)
266    };
267trans_format(number, Val, Val1, Val2) ->
268    {
269        ?W(Val, 10),
270        ?W(Val1, 10),
271        ?W(Val2, 10)
272    }.
273
274title(Type, Type1, Type2) ->
275    ?render([
276        ?UNDERLINE,
277        ?GRAY_BG,
278        ?W("NO", 2),
279        ?W("Port", 16),
280        ?W(erlang:atom_to_list(Type), 10),
281        ?W(Type1, 10),
282        ?W(Type2, 10),
283        ?W("output", 12),
284        ?W("input", 12),
285        ?W("queuesize", 6),
286        ?W("memory", 12),
287        ?W("Peername(ip:port)", 19)
288    ]).
289
290get_remote_ip(P) ->
291    case inet:peername(P) of
292        {ok, {Addr, Port}} ->
293            AddrList = [
294                begin
295                    erlang:integer_to_list(A)
296                end
297                || A <- erlang:tuple_to_list(Addr)
298            ],
299            string:join(AddrList, ".") ++ ":" ++ erlang:integer_to_list(Port);
300        {error, Err} ->
301            inet:format_error(Err)
302    end.
303
304add_choose_color(ChoosePos, ChoosePos, R) -> [?CHOOSE_BG | R];
305add_choose_color(_ChoosePos, _CurPos, R) -> R.
306