%%% @author zhongwen -module(observer_cli_inet). -include("observer_cli.hrl"). %% API -export([start/1]). -ifdef(TEST). -export([ trans_type/1, trans_format/4, get_menu_str/4, title/3, add_choose_color/3, collect_io_info/1, collect_inet_info/5, collect_inet_render_info/3, render_inet_rows/3, render_io_info/1, render_io_rows/1, inet_info/5, inet_window/4, getstat/2, get_remote_ip/1, start_port_view/4 ]). -endif. -define(LAST_LINE, "q(quit) ic(inet_count) iw(inet_window) rc(recv_cnt) ro(recv_oct)" " sc(send_cnt) so(send_oct) cnt oct 9(port 9 info) pd/pu(page:down/up)" ). -spec start(view_opts()) -> no_return(). start(#view_opts{inet = InetOpt, auto_row = AutoRow} = ViewOpts) -> StorePid = observer_cli_store:start(), RenderPid = spawn_link( fun() -> ?output(?CLEAR), {{input, In}, {output, Out}} = erlang:statistics(io), render_worker(StorePid, InetOpt, ?INIT_TIME_REF, 0, {In, Out}, AutoRow) end ), manager(StorePid, RenderPid, ViewOpts). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Private %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% manager(StorePid, RenderPid, ViewOpts = #view_opts{inet = InetOpts}) -> #inet{cur_page = CurPage, pages = Pages} = InetOpts, case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [RenderPid, StorePid]) of quit -> observer_cli_lib:exit_processes([StorePid]), erlang:send(RenderPid, quit); {new_interval, NewInterval} -> erlang:send(RenderPid, {new_interval, NewInterval}), NewInet = InetOpts#inet{interval = NewInterval}, manager(StorePid, RenderPid, ViewOpts#view_opts{inet = NewInet}); Func when Func =:= inet_count; Func =:= inet_window -> restart(StorePid, RenderPid, ViewOpts#view_opts{inet = InetOpts#inet{func = Func}}); Type when Type =:= recv_cnt; Type =:= recv_oct; Type =:= send_cnt; Type =:= send_oct; Type =:= cnt; Type =:= oct -> restart(StorePid, RenderPid, ViewOpts#view_opts{inet = InetOpts#inet{type = Type}}); {jump, NewPos} -> NewPages = observer_cli_lib:update_page_pos(CurPage, NewPos, Pages), NewInetOpts = InetOpts#inet{pages = NewPages}, start_port_view(StorePid, RenderPid, ViewOpts#view_opts{inet = NewInetOpts}, false); jump -> start_port_view(StorePid, RenderPid, ViewOpts, true); page_down_top_n -> restart_page(StorePid, RenderPid, ViewOpts, CurPage, 1); page_up_top_n -> restart_page(StorePid, RenderPid, ViewOpts, CurPage, -1); _ -> manager(StorePid, RenderPid, ViewOpts) end. restart_page( StorePid, RenderPid, ViewOpts = #view_opts{inet = InetOpts = #inet{pages = Pages}}, CurPage, Delta ) -> NewPage = observer_cli_lib:next_page(CurPage, Delta), NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages), restart(StorePid, RenderPid, ViewOpts#view_opts{ inet = InetOpts#inet{ cur_page = NewPage, pages = NewPages } }). restart(StorePid, RenderPid, ViewOpts) -> observer_cli_lib:exit_processes([StorePid, RenderPid]), start(ViewOpts). render_worker(StorePid, InetOpt, LastTimeRef, Count, LastIO, AutoRow) -> #inet{func = Function, type = Type, interval = Interval, cur_page = CurPage} = InetOpt, TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow), Row = erlang:max(TerminalRow - 5, 0), Text = get_menu_str(Function, Type, Interval, Row), Menu = observer_cli_lib:render_top_menu(inet, Text), TopLen = Row * CurPage, InetList = collect_inet_info(Function, Type, TopLen, Interval, Count), {IOInfo, NewIO} = collect_io_info(LastIO), IORows = render_io_info(IOInfo), InetInfo = collect_inet_render_info(InetList, TopLen, InetOpt), {PortList, InetRows} = render_inet_rows(InetInfo, TopLen, InetOpt), LastLine = observer_cli_lib:render_footer(?LAST_LINE), ?output([?CURSOR_TOP, Menu, IORows, InetRows, LastLine]), observer_cli_store:update(StorePid, Row, PortList), NewInterval = case Function of inet_count -> Interval; inet_window -> 10 end, TimeRef = observer_cli_lib:next_redraw(LastTimeRef, NewInterval), receive {new_interval, NewInterval} -> ?output(?CLEAR), render_worker( StorePid, InetOpt#inet{interval = NewInterval}, TimeRef, Count + 1, NewIO, AutoRow ); quit -> quit; _ -> render_worker(StorePid, InetOpt, TimeRef, Count + 1, NewIO, AutoRow) end. collect_io_info({LastIn, LastOut}) -> {{input, In}, {output, Out}} = erlang:statistics(io), { #{ input_delta => In - LastIn, output_delta => Out - LastOut, total_input => In, total_output => Out }, {In, Out} }. render_io_info(#{ input_delta := InputDelta, output_delta := OutputDelta, total_input := In, total_output := Out }) -> [ ByteInputW, InputDeltaW, ByteOutputW, OutputDeltaW, TotalInputW, TotalInW, TotalOutputW, TotalOutW ] = io_widths(), ?render([ ?YELLOW, ?W("Byte Input", ByteInputW), ?W({byte, InputDelta}, InputDeltaW), ?W("Byte Output", ByteOutputW), ?W({byte, OutputDelta}, OutputDeltaW), ?W("Total Input", TotalInputW), ?W({byte, In}, TotalInW), ?W("Total Output", TotalOutputW), ?W({byte, Out}, TotalOutW) ]). -ifdef(TEST). render_io_rows(LastIO) -> {IOInfo, NewIO} = collect_io_info(LastIO), {render_io_info(IOInfo), NewIO}. -endif. collect_inet_render_info([], _Num, _InetOpt) -> []; collect_inet_render_info(InetList, Num, #inet{ type = Type, pages = Pages, cur_page = Page }) -> {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(InetList)), Rows = lists:sublist(InetList, Start, Num), collect_inet_rows(Type, Rows, Start, ChoosePos). collect_inet_rows(Type, Rows, Start, ChoosePos) -> {_, Collected} = lists:foldl( fun(Item, {Pos, Acc}) -> {Pos + 1, [collect_inet_row(Type, Item, Pos, ChoosePos) | Acc]} end, {Start, []}, Rows ), lists:reverse(Collected). collect_inet_row(Type, Item, Pos, ChoosePos) when Type =:= cnt orelse Type =:= oct -> {Port, Value, [{_, Type1Value}, {_, Type2Value}]} = Item, collect_inet_row(Port, Value, Type1Value, Type2Value, Pos, ChoosePos); collect_inet_row(Type, {Port, Value, Stats}, Pos, ChoosePos) -> {_, Type1, _} = trans_type(Type), OtherType = erlang:list_to_existing_atom(Type1), Packet1 = case proplists:get_value(OtherType, Stats) of undefined -> getstat(Port, OtherType); OtherValue -> OtherValue end, AllPacket = case is_integer(Packet1) of true -> Value + Packet1; false -> Packet1 end, collect_inet_row(Port, Value, Packet1, AllPacket, Pos, ChoosePos). collect_inet_row(Port, Value, Type1Value, Type2Value, Pos, ChoosePos) -> {memory_used, MemoryUsed} = recon:port_info(Port, memory_used), {io, IO} = recon:port_info(Port, io), #{ pos => Pos, choose_pos => ChoosePos, port => Port, value => Value, type1 => Type1Value, type2 => Type2Value, input => proplists:get_value(input, IO), output => proplists:get_value(output, IO), queue_size => proplists:get_value(queue_size, MemoryUsed), memory => proplists:get_value(memory, MemoryUsed), peer => get_remote_ip(Port) }. render_inet_rows([], Rows, #inet{func = inet_count, type = Type}) -> {[], io_lib:format("\e[32;1mGet nothing for recon:inet_count(~p, ~p)\e[0m~n", [Type, Rows])}; render_inet_rows([], Rows, #inet{func = inet_window, type = Type, interval = Interval}) -> { [], io_lib:format("\e[32;1mGet nothing for recon:inet_window(~p, ~p, ~p)\e[0m~n", [ Type, Rows, Interval ]) }; render_inet_rows(InetList, _Num, #inet{type = Type}) when Type =:= cnt orelse Type =:= oct -> {Unit, RecvType, SendType} = trans_type(Type), Title = title(Type, RecvType, SendType), [NoW, PortW, ValueW, RecvW, SendW, OutputW, InputW, QueueW, MemoryW, PeerW] = inet_widths(), FormatFunc = fun(Item, {Acc, Acc1}) -> #{ pos := Pos, choose_pos := ChoosePos, port := Port, value := Value, type1 := Recv, type2 := Send, input := Input, output := Output, queue_size := QueueSize, memory := Memory, peer := IP } = Item, {ValueFormat, RecvFormat, SendFormat} = trans_format( Unit, Value, Recv, Send, [ValueW, RecvW, SendW] ), R = [ ?W(Pos, NoW), ?W(Port, PortW), ValueFormat, RecvFormat, SendFormat, ?W({byte, Output}, OutputW), ?W({byte, Input}, InputW), ?W(QueueSize, QueueW), ?W({byte, Memory}, MemoryW), ?W(IP, PeerW) ], Rows = add_choose_color(ChoosePos, Pos, R), {[?render(Rows) | Acc], [{Pos, Port} | Acc1]} end, {Rows, PortList} = lists:foldl( FormatFunc, {[], []}, InetList ), {PortList, [Title | lists:reverse(Rows)]}; render_inet_rows(InetList, _Num, #inet{type = Type}) -> {Unit, Type1, Type2} = trans_type(Type), Title = title(Type, Type1, Type2), [NoW, PortW, ValueW, Type1W, Type2W, OutputW, InputW, QueueW, MemoryW, PeerW] = inet_widths(), FormatFunc = fun(Item, {Acc, Acc1}) -> #{ pos := Pos, choose_pos := ChoosePos, port := Port, value := Value, type1 := Packet1, type2 := AllPacket, input := Input, output := Output, queue_size := QueueSize, memory := Memory, peer := IP } = Item, {ValueFormat, Packet1Format, AllFormat} = trans_format( Unit, Value, Packet1, AllPacket, [ValueW, Type1W, Type2W] ), R = [ ?W(Pos, NoW), ?W(Port, PortW), ValueFormat, Packet1Format, AllFormat, ?W({byte, Output}, OutputW), ?W({byte, Input}, InputW), ?W(QueueSize, QueueW), ?W({byte, Memory}, MemoryW), ?W(IP, PeerW) ], Rows = add_choose_color(ChoosePos, Pos, R), {[?render(Rows) | Acc], [{Pos, Port} | Acc1]} end, {Rows, PortList} = lists:foldl( FormatFunc, {[], []}, InetList ), {PortList, [Title | lists:reverse(Rows)]}. get_menu_str(inet_count, Type, Interval, Rows) -> io_lib:format("recon:inet_count(~p, ~w) Interval:~wms", [Type, Rows, Interval]); get_menu_str(inet_window, Type, Interval, Rows) -> io_lib:format("recon:inet_window(~p, ~w, ~w) Interval:~wms", [Type, Rows, Interval, Interval]). collect_inet_info(Function, Type, Num, Ms, Count) -> inet_info(Function, Type, Num, Ms, Count). inet_info(inet_count, Type, Num, _, _) -> recon:inet_count(Type, Num); inet_info(inet_window, Type, Num, _, 0) -> recon:inet_count(Type, Num); inet_info(inet_window, Type, Num, Ms, _) -> {Unit, _, _} = trans_type(Type), CombinedType = case Unit of number -> cnt; byte -> oct end, Sample = fun() -> recon_lib:inet_attrs(CombinedType) end, {First, Last} = recon_lib:sample(Ms, Sample), inet_window(Type, Num, First, Last). inet_window(Type, Num, First, Last) -> {Unit, _, _} = trans_type(Type), DirectionTypes = case Unit of number -> [recv_cnt, send_cnt]; byte -> [recv_oct, send_oct] end, MetricValues = fun(Metric, Items) -> [ {Port, proplists:get_value(Metric, Stats), []} || {Port, _, Stats} <- Items ] end, DirectionWindows = maps:from_list([ {Metric, recon_lib:sliding_window(MetricValues(Metric, First), MetricValues(Metric, Last))} || Metric <- DirectionTypes ]), SelectedWindow = case Type of cnt -> recon_lib:sliding_window(First, Last); oct -> recon_lib:sliding_window(First, Last); _ -> maps:get(Type, DirectionWindows) end, Selected = recon_lib:sublist_top_n_attrs(SelectedWindow, Num), DirectionValues = [ {Metric, maps:from_list([ {Port, Value} || {Port, Value, _} <- maps:get(Metric, DirectionWindows) ])} || Metric <- DirectionTypes ], [ {Port, Value, [{Metric, maps:get(Port, Values)} || {Metric, Values} <- DirectionValues]} || {Port, Value, _} <- Selected ]. getstat(Port, Attr) -> case inet:getstat(Port, [Attr]) of {ok, [{_, Value}]} -> Value; {error, Err} -> inet:format_error(Err) end. start_port_view(StorePid, RenderPid, Opts = #view_opts{inet = InetOpt}, AutoJump) -> #inet{cur_page = CurPage, pages = Pages} = InetOpt, {_, CurPos} = lists:keyfind(CurPage, 1, Pages), case observer_cli_store:lookup_pos(StorePid, CurPos) of {CurPos, ChoosePort} -> observer_cli_lib:exit_processes([StorePid, RenderPid]), observer_cli_port:start(ChoosePort, Opts); {_, ChoosePort} when AutoJump -> observer_cli_lib:exit_processes([StorePid, RenderPid]), observer_cli_port:start(ChoosePort, Opts); _ -> manager(StorePid, RenderPid, Opts) end. trans_type(cnt) -> {number, "recv_cnt", "send_cnt"}; trans_type(oct) -> {byte, "recv_oct", "send_oct"}; trans_type(send_cnt) -> {number, "recv_cnt", "cnt"}; trans_type(recv_cnt) -> {number, "send_cnt", "cnt"}; trans_type(send_oct) -> {byte, "recv_oct", "oct"}; trans_type(recv_oct) -> {byte, "send_oct", "oct"}. -ifdef(TEST). trans_format(byte, Val, Val1, Val2) -> { ?W({byte, Val}, 10), ?W({byte, Val1}, 10), ?W({byte, Val2}, 10) }; trans_format(number, Val, Val1, Val2) -> { ?W(Val, 10), ?W(Val1, 10), ?W(Val2, 10) }. -endif. trans_format(byte, Val, Val1, Val2, [ValW, Val1W, Val2W]) -> { ?W({byte, Val}, ValW), ?W({byte, Val1}, Val1W), ?W({byte, Val2}, Val2W) }; trans_format(number, Val, Val1, Val2, [ValW, Val1W, Val2W]) -> { ?W(Val, ValW), ?W(Val1, Val1W), ?W(Val2, Val2W) }. title(Type, Type1, Type2) -> [NoW, PortW, TypeW, Type1W, Type2W, OutputW, InputW, QueueW, MemoryW, PeerW] = inet_widths(), ?render([ ?UNDERLINE, ?GRAY_BG, ?W("NO", NoW), ?W("Port", PortW), ?W(erlang:atom_to_list(Type), TypeW), ?W(Type1, Type1W), ?W(Type2, Type2W), ?W("output", OutputW), ?W("input", InputW), ?W("queuesize", QueueW), ?W("memory", MemoryW), ?W("Peername(ip:port)", PeerW) ]). io_widths() -> observer_cli_lib:weighted_widths( [14, 12, 13, 12, 15, 17, 15, 17], [0, 1, 0, 1, 0, 1, 0, 1] ). inet_widths() -> observer_cli_lib:weighted_widths( [2, 16, 10, 10, 10, 12, 12, 6, 12, 19], [0, 3, 3, 3, 3, 1, 1, 0, 1, 4] ). get_remote_ip(P) -> case inet:peername(P) of {ok, {Addr, Port}} -> AddrList = [ begin erlang:integer_to_list(A) end || A <- erlang:tuple_to_list(Addr) ], string:join(AddrList, ".") ++ ":" ++ erlang:integer_to_list(Port); {error, Err} -> inet:format_error(Err) end. add_choose_color(ChoosePos, ChoosePos, R) -> [?CHOOSE_BG | R]; add_choose_color(_ChoosePos, _CurPos, R) -> R.