%%% @author zhongwen -module(observer_cli). -include("observer_cli.hrl"). %% proc_lib:get_label/1 is not exported before OTP 27 -dialyzer([{nowarn_function, [choose_label/1]}]). -ignore_xref({proc_lib, get_label, 1}). %% API -export([start/0]). -export([start/1]). -export([start/2]). -export([start_plugin/0]). -export([clean/1]). %% cpu >= this value will be highlight -define(CPU_ALARM_THRESHOLD, 0.8). %% port or process reach max_limit * 0.85 will be highlight -define(COUNT_ALARM_THRESHOLD, 0.85). -define(LAST_LINE, "q(quit) p(pause) r/rr(reduction) m/mm(mem)" "b/bb(binary mem) t/tt(total heap size) mq/mmq(msg queue) 9(proc 9 info) F/B(page forward/back)" ). -spec start() -> no_return() | {badrpc, term()}. start() -> start(#view_opts{}). -spec start(Node) -> no_return() | {badrpc, term()} when Node :: atom() | non_neg_integer() | view_opts(). start(Node) when Node =:= node() -> start(#view_opts{}); start(Node) when is_atom(Node) -> rpc_start(Node, ?DEFAULT_INTERVAL); start(Opts = #view_opts{home = Home}) -> erlang:process_flag(trap_exit, true), AutoRow = check_auto_row(), #home{scheduler_usage = SchUsage} = Home, StorePid = observer_cli_store:start(), LastSchWallFlag = set_scheduler_wall_time(true, SchUsage), PsCmd = io_lib:format("ps -o pcpu,pmem ~s", [os:getpid()]), RenderPid = spawn_link(fun() -> render_worker(PsCmd, StorePid, Home, AutoRow) end), manager(StorePid, RenderPid, Opts#view_opts{auto_row = AutoRow}, LastSchWallFlag); start(Interval) when is_integer(Interval), Interval >= ?MIN_INTERVAL -> start(#view_opts{ home = #home{interval = Interval}, ets = #ets{interval = Interval}, sys = #system{interval = Interval}, db = #db{interval = Interval}, help = #help{interval = Interval}, inet = #inet{interval = Interval}, process = #process{interval = Interval}, port = Interval }). -spec start(Node, Cookies | Options) -> no_return() | {badrpc, term()} when Node :: atom(), Cookies :: atom(), Options :: proplists:proplist(). start(Node, _Cookie) when Node =:= node() -> start(#view_opts{}); start(Node, Cookie) when is_atom(Node) andalso is_atom(Cookie) -> start(Node, [{cookie, Cookie}]); start(Node, Options) when is_atom(Node) andalso is_list(Options) -> case proplists:get_value(cookie, Options) of undefined -> ok; Cookie -> erlang:set_cookie(Node, Cookie) end, Interval = proplists:get_value(interval, Options, ?DEFAULT_INTERVAL), rpc_start(Node, Interval). -spec start_plugin() -> no_return(). start_plugin() -> erlang:process_flag(trap_exit, true), application:ensure_all_started(observer_cli), observer_cli_plugin:start(#view_opts{}). -spec clean(list()) -> boolean(). clean([RenderPid, StorePid, SchWallFlag, SchUsage]) -> observer_cli_lib:exit_processes([RenderPid, StorePid]), set_scheduler_wall_time(SchWallFlag, SchUsage). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Private %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% rpc_start(Node, Interval) -> case net_kernel:hidden_connect_node(Node) of true -> rpc:call(Node, ?MODULE, start, [Interval]); false -> Msg = <<"Node(~p) refuse to be connected, make sure cookie is valid~n">>, connect_error(Msg, Node), {badrpc, nodedown}; ignored -> Msg = <<"Ignored by node(~p), local node is not alive!~n">>, connect_error(Msg, Node), {badrpc, nodedown} end. manager(StorePid, RenderPid, Opts, LastSchWallFlag) -> #view_opts{home = Home = #home{cur_page = CurPage, pages = Pages, scheduler_usage = SchUsage}} = Opts, Resource = [RenderPid, StorePid, LastSchWallFlag, SchUsage], case observer_cli_lib:parse_cmd(Opts, ?MODULE, Resource) of quit -> erlang:unlink(RenderPid), erlang:send(RenderPid, quit), set_scheduler_wall_time(LastSchWallFlag, SchUsage), observer_cli_lib:exit_processes([StorePid]), quit; pause_or_resume -> erlang:send(RenderPid, pause_or_resume), manager(StorePid, RenderPid, Opts, LastSchWallFlag); {new_interval, NewInterval} -> clean(Resource), start(Opts#view_opts{home = Home#home{interval = NewInterval}}); scheduler_usage -> NewSchUsage = case SchUsage of ?DISABLE -> ?ENABLE; ?ENABLE -> ?DISABLE end, clean(Resource), start(Opts#view_opts{home = Home#home{scheduler_usage = NewSchUsage}}); {jump, NewPos} -> NewPages = observer_cli_lib:update_page_pos(CurPage, NewPos, Pages), NewOpts = Opts#view_opts{home = Home#home{pages = NewPages}}, start_process_view(StorePid, RenderPid, NewOpts, LastSchWallFlag, false); jump -> start_process_view(StorePid, RenderPid, Opts, LastSchWallFlag, true); {func, Func, Type} -> clean(Resource), start(Opts#view_opts{home = Home#home{func = Func, type = Type}}); page_down_top_n -> NewPage = max(CurPage + 1, 1), NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages), clean(Resource), start(Opts#view_opts{home = Home#home{cur_page = NewPage, pages = NewPages}}); page_up_top_n -> NewPage = max(CurPage - 1, 1), NewPages = observer_cli_lib:update_page_pos(StorePid, NewPage, Pages), clean(Resource), start(Opts#view_opts{home = Home#home{cur_page = NewPage, pages = NewPages}}); {go_to_pid, Pid} -> clean(Resource), observer_cli_process:start(home, Pid, Opts); _ -> manager(StorePid, RenderPid, Opts, LastSchWallFlag) end. render_worker(PsCmd, Manager, Home = #home{scheduler_usage = SchUsage}, AutoRow) -> ?output(?CLEAR), StableInfo = get_stable_system_info(), LastStats = get_incremental_stats(SchUsage), redraw_running(PsCmd, Manager, Home, StableInfo, LastStats, erlang:make_ref(), AutoRow, true). %% pause status waiting to be resume redraw_pause(PsCmd, StorePid, Home, StableInfo, LastStats, LastTimeRef, AutoRow) -> notify_pause_status(), erlang:cancel_timer(LastTimeRef), #home{func = Func, type = Type} = Home, receive quit -> quit; {Func, Type} -> redraw_running( PsCmd, StorePid, Home, StableInfo, LastStats, LastTimeRef, AutoRow, false ); pause_or_resume -> ?output(?CLEAR), redraw_running(PsCmd, StorePid, Home, StableInfo, LastStats, LastTimeRef, AutoRow, true) end. %% running status redraw_running(PsCmd, StorePid, Home, StableInfo, LastStats, LastTimeRef, AutoRow, IsFirstTime) -> #home{ interval = Interval, func = Func, type = Type, pages = RankPos, cur_page = CurPage, scheduler_usage = SchUsage } = Home, erlang:cancel_timer(LastTimeRef), TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow), {Diffs, Schedulers, NewStats} = node_stats(LastStats, SchUsage), {CPURow, CPULine} = render_scheduler_usage(Schedulers), ProcessRows = max(TerminalRow - 14 - CPURow, 0), TopLen = ProcessRows * CurPage, TopList = get_top_n(Func, Type, Interval, TopLen, IsFirstTime), Text = get_refresh_prompt(Func, Type, Interval, TopLen), MenuLine = observer_cli_lib:render_menu(home, Text), SystemLine = render_system_line(PsCmd, element(1, StableInfo)), MemLine = render_memory_process_line(Diffs, element(2, StableInfo), Interval), {TopNList, RankLine} = render_top_n_view(Type, TopList, ProcessRows, RankPos, CurPage), LastLine = observer_cli_lib:render_last_line(?LAST_LINE), ?output([?CURSOR_TOP, MenuLine, SystemLine, MemLine, CPULine, RankLine, LastLine]), observer_cli_store:update(StorePid, ProcessRows, TopNList), TimeRef = refresh_next_time(Func, Type, Interval), receive quit -> quit; pause_or_resume -> redraw_pause(PsCmd, StorePid, Home, StableInfo, NewStats, TimeRef, AutoRow); {Func, Type} -> redraw_running(PsCmd, StorePid, Home, StableInfo, NewStats, TimeRef, AutoRow, false) end. render_system_line(PsCmd, StableInfo) -> [Version, SysVersion, ProcLimit, PortLimit, EtsLimit] = StableInfo, ActiveTask = erlang:statistics(total_active_tasks), {ContextSwitch, _} = erlang:statistics(context_switches), AtomStatus = get_atom_status(), Reductions = erlang:statistics(reductions), {PortWarning, ProcWarning, PortCount, ProcCount} = get_port_proc_info(PortLimit, ProcLimit), CmdValue = case string:split(os:cmd(PsCmd), "\n", all) of [_, CmdValueTmp | _] -> CmdValueTmp; _ -> "" end, [CpuPsV, MemPsV] = case lists:filter(fun(Y) -> Y =/= [] end, string:split(CmdValue, " ", all)) of [V1, V2] -> [V1, V2]; _ -> ["--", "--"] end, Title = ?render([ ?W(SysVersion, 136), ?NEW_LINE, ?GRAY_BG, ?W("System", 10), ?W("Count/Limit", 21), ?W("System", 25), ?W("Status", 21), ?W("Stat Info", 20), ?W("Size", 24) ]), Row1 = ?render([ ?W("Proc Count", 10), ?W2(ProcWarning, ProcCount, 22), ?W(" Version", 26), ?W(Version, 21), ?W("Active Task", 20), ?W(ActiveTask, 24), ?NEW_LINE, ?W("Port Count", 10), ?W2(PortWarning, PortCount, 22), ?W(" ps -o pcpu", 26), ?W([CpuPsV, "%"], 21), ?W("Context Switch", 20), ?W(ContextSwitch, 24) ]), {Reds, AddReds} = Reductions, Row2 = case AtomStatus of {ok, AtomLimit, AtomCount} -> {AtomWarning, Atom} = format_atom_info(AtomLimit, AtomCount), ?render([ ?UNDERLINE, ?W("Atom Count", 10), ?W2(AtomWarning, Atom, 22), ?W(" ps -o pmem", 26), ?W([MemPsV, "%"], 21), ?W("Reds(Total/SinceLastCall)", 20), ?W([integer_to_list(Reds), "/", integer_to_list(AddReds)], 24) ]); {error, unsupported} -> ?render([ ?UNDERLINE, ?W("Ets Limit", 10), ?W(EtsLimit, 21), ?W(" ps -o pmem", 25), ?W([MemPsV, "%"], 21), ?W("Reductions", 20), ?W(Reductions, 24) ]) end, [Title, Row1, Row2]. render_memory_process_line(MemSum, PortParallelism, Interval) -> RunQ = erlang:statistics(run_queue), Mem = erlang:memory(), TotalMem = proplists:get_value(total, Mem), ProcMem = proplists:get_value(processes_used, Mem), CodeMem = proplists:get_value(code, Mem), AtomMem = proplists:get_value(atom_used, Mem), BinMem = proplists:get_value(binary, Mem), EtsMem = proplists:get_value(ets, Mem), EtsLen = erlang:length(ets:all()), { BytesIn, BytesOut, GcCount, GcWordsReclaimed } = MemSum, {Queue, LogKey} = case whereis(error_logger) of undefined -> {erlang:integer_to_list(RunQ), "RunQueue"}; Pid -> {_, Q} = process_info(Pid, message_queue_len), { [erlang:integer_to_list(RunQ), "/", erlang:integer_to_list(Q)], "RunQueue/ErrorLoggerQueue" } end, ProcMemPercent = observer_cli_lib:to_percent(ProcMem / TotalMem), AtomMemPercent = observer_cli_lib:to_percent(AtomMem / TotalMem), BinMemPercent = observer_cli_lib:to_percent(BinMem / TotalMem), CodeMemPercent = observer_cli_lib:to_percent(CodeMem / TotalMem), EtsMemPercent = observer_cli_lib:to_percent(EtsMem / TotalMem), Title = ?render([ ?GRAY_BG, ?W("Mem Type", 10), ?W("Size", 21), ?W("Mem Type", 25), ?W("Size", 21), ?W(["IO/GC:(", integer_to_binary(Interval), "ms)"], 20), ?W("Total/Increments", 24) ]), Row = ?render([ ?W("Total", 10), ?W({byte, TotalMem}, 12), ?W("100.0%", 6), ?W("Binary", 25), ?W({byte, BinMem}, 12), ?W(BinMemPercent, 6), ?W("IO Output", 20), ?W(BytesOut, 24), ?NEW_LINE, ?W("Process", 10), ?W({byte, ProcMem}, 12), ?W(ProcMemPercent, 6), ?W("Code", 25), ?W({byte, CodeMem}, 12), ?W(CodeMemPercent, 6), ?W("IO Input", 20), ?W(BytesIn, 24), ?NEW_LINE, ?W("Atom", 10), ?W({byte, AtomMem}, 12), ?W(AtomMemPercent, 6), ?W("Port Parallelism (+spp)", 25), ?W(PortParallelism, 21), ?W("Gc Count", 20), ?W(GcCount, 24), ?NEW_LINE, ?W("Ets/" ++ erlang:integer_to_list(EtsLen), 10), ?W({byte, EtsMem}, 12), ?W(EtsMemPercent, 6), ?W(LogKey, 25), ?W(Queue, 21), ?W("Gc Words Reclaimed", 20), ?W(GcWordsReclaimed, 24) ]), [Title, Row]. render_scheduler_usage(undefined) -> {0, []}; render_scheduler_usage(SchedulerUsage) -> SchedulerNum = erlang:length(SchedulerUsage), render_scheduler_usage(SchedulerUsage, SchedulerNum). %% < 8 core split 2 part render_scheduler_usage(SchedulerUsage, SchedulerNum) when SchedulerNum < 8 -> Column = case SchedulerNum rem 2 =:= 0 of true -> SchedulerNum div 2; false -> (SchedulerNum div 2) + 1 end, CPU = [ begin Seq2 = transform_seq(Seq1, Column, SchedulerNum), Percent1 = proplists:get_value(Seq1, SchedulerUsage, 0.0), Percent2 = proplists:get_value(Seq2, SchedulerUsage, 0.0), CPU1 = observer_cli_lib:to_percent(Percent1), CPU2 = observer_cli_lib:to_percent(Percent2), Process1 = lists:duplicate(trunc(Percent1 * 57), "|"), Process2 = lists:duplicate(trunc(Percent2 * 57), "|"), IsLastLine = Seq1 =:= Column, Format = process_bar_format_style([Percent1, Percent2], IsLastLine), io_lib:format(Format, [ Seq1, Process1, CPU1, Seq2, Process2, CPU2 ]) end || Seq1 <- lists:seq(1, Column) ], {Column, CPU}; %% 100 >= scheduler >= 8 split 4 part render_scheduler_usage(SchedulerUsage, SchedulerNum) when SchedulerNum =< 100 -> Column = case SchedulerNum rem 4 =:= 0 of true -> SchedulerNum div 4; false -> (SchedulerNum div 4) + 1 end, CPU = [ begin Seq2 = transform_seq(Seq1, Column, SchedulerNum), Seq3 = transform_seq(Seq2, Column, SchedulerNum), Seq4 = transform_seq(Seq3, Column, SchedulerNum), Percent1 = proplists:get_value(Seq1, SchedulerUsage, 0.0), Percent2 = proplists:get_value(Seq2, SchedulerUsage, 0.0), Percent3 = proplists:get_value(Seq3, SchedulerUsage, 0.0), Percent4 = proplists:get_value(Seq4, SchedulerUsage, 0.0), CPU1 = observer_cli_lib:to_percent(Percent1), CPU2 = observer_cli_lib:to_percent(Percent2), CPU3 = observer_cli_lib:to_percent(Percent3), CPU4 = observer_cli_lib:to_percent(Percent4), Process1 = lists:duplicate(trunc(Percent1 * 22), "|"), Process2 = lists:duplicate(trunc(Percent2 * 22), "|"), Process3 = lists:duplicate(trunc(Percent3 * 22), "|"), Process4 = lists:duplicate(trunc(Percent4 * 23), "|"), IsLastLine = Seq1 =:= Column, Format = process_bar_format_style([Percent1, Percent2, Percent3, Percent4], IsLastLine), io_lib:format(Format, [ Seq1, Process1, CPU1, Seq2, Process2, CPU2, Seq3, Process3, CPU3, Seq4, Process4, CPU4 ]) end || Seq1 <- lists:seq(1, Column) ], {Column, CPU}; %% scheduler > 100 don't show process bar. render_scheduler_usage(SchedulerUsage, SchedulerNum) -> Column = case SchedulerNum rem 10 =:= 0 of true -> SchedulerNum div 10; false -> (SchedulerNum div 10) + 1 end, CPU = [ begin Seq2 = transform_seq(Seq1, Column, SchedulerNum), Seq3 = transform_seq(Seq2, Column, SchedulerNum), Seq4 = transform_seq(Seq3, Column, SchedulerNum), Seq5 = transform_seq(Seq4, Column, SchedulerNum), Seq6 = transform_seq(Seq5, Column, SchedulerNum), Seq7 = transform_seq(Seq6, Column, SchedulerNum), Seq8 = transform_seq(Seq7, Column, SchedulerNum), Seq9 = transform_seq(Seq8, Column, SchedulerNum), Seq10 = transform_seq(Seq9, Column, SchedulerNum), Percent1 = proplists:get_value(Seq1, SchedulerUsage), Percent2 = proplists:get_value(Seq2, SchedulerUsage), Percent3 = proplists:get_value(Seq3, SchedulerUsage), Percent4 = proplists:get_value(Seq4, SchedulerUsage), Percent5 = proplists:get_value(Seq5, SchedulerUsage), Percent6 = proplists:get_value(Seq6, SchedulerUsage), Percent7 = proplists:get_value(Seq7, SchedulerUsage), Percent8 = proplists:get_value(Seq8, SchedulerUsage), Percent9 = proplists:get_value(Seq9, SchedulerUsage), Percent10 = proplists:get_value(Seq10, SchedulerUsage), CPU1 = observer_cli_lib:to_percent(Percent1), CPU2 = observer_cli_lib:to_percent(Percent2), CPU3 = observer_cli_lib:to_percent(Percent3), CPU4 = observer_cli_lib:to_percent(Percent4), CPU5 = observer_cli_lib:to_percent(Percent5), CPU6 = observer_cli_lib:to_percent(Percent6), CPU7 = observer_cli_lib:to_percent(Percent7), CPU8 = observer_cli_lib:to_percent(Percent8), CPU9 = observer_cli_lib:to_percent(Percent9), CPU10 = observer_cli_lib:to_percent(Percent10), IsLastLine = Seq1 =:= Column, Percents = [ Percent1, Percent2, Percent3, Percent4, Percent5, Percent6, Percent7, Percent8, Percent9, Percent10 ], Format = process_bar_format_style(Percents, IsLastLine), io_lib:format(Format, [ Seq1, CPU1, Seq2, CPU2, Seq3, CPU3, Seq4, CPU4, Seq5, CPU5, Seq6, CPU6, Seq7, CPU7, Seq8, CPU8, Seq9, CPU9, Seq10, CPU10 ]) end || Seq1 <- lists:seq(1, Column) ], {Column, CPU}. transform_seq(Seq, Column, Total) -> Num = Seq + Column, case Num > Total of true -> 1000; false -> Num end. render_top_n_view(memory, MemoryList, Num, Pages, Page) -> Title = ?render([ ?W2(?GRAY_BG, "No | Pid", 16), ?W2(?RED_BG, " Memory", 14), ?W(?GRAY_BG, "Name|>Label|>Initial Call", 45), ?W(?GRAY_BG, " Reductions", 14), ?W(?GRAY_BG, " MsgQueue", 10), ?W(?GRAY_BG, "Current Function", 32) ]), {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(MemoryList)), FormatFunc = fun(Item, {Acc, Acc1, Pos}) -> {Pid, MemVal, CurFun, NameOrCall} = get_top_n_info(Item), {Reductions, MsgQueueLen} = get_pid_info(Pid, [reductions, message_queue_len]), Format = get_memory_format(ChoosePos, Pos), R = io_lib:format( Format, [ Pos, erlang:pid_to_list(Pid), observer_cli_lib:to_byte(MemVal), NameOrCall, observer_cli_lib:to_list(Reductions), observer_cli_lib:to_list(MsgQueueLen), CurFun ] ), {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1} end, {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(MemoryList, Start, Num)), {PidList, [Title | lists:reverse(Rows)]}; render_top_n_view(binary_memory, MemoryList, Num, Pages, Page) -> Title = ?render([ ?W2(?GRAY_BG, "No | Pid", 16), ?W2(?RED_BG, " BinMemory", 14), ?W(?GRAY_BG, "Name|>Label|>Initial Call", 45), ?W(?GRAY_BG, " Reductions", 14), ?W(?GRAY_BG, " MsgQueue", 10), ?W(?GRAY_BG, "Current Function", 32) ]), {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(MemoryList)), FormatFunc = fun(Item, {Acc, Acc1, Pos}) -> {Pid, MemVal, CurFun, NameOrCall} = get_top_n_info(Item), {Reductions, MsgQueueLen} = get_pid_info(Pid, [reductions, message_queue_len]), Format = get_memory_format(ChoosePos, Pos), R = io_lib:format( Format, [ Pos, pid_to_list(Pid), observer_cli_lib:to_byte(MemVal), NameOrCall, observer_cli_lib:to_list(Reductions), observer_cli_lib:to_list(MsgQueueLen), CurFun ] ), {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1} end, {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(MemoryList, Start, Num)), {PidList, [Title | lists:reverse(Rows)]}; render_top_n_view(reductions, ReductionList, Num, Pages, Page) -> Title = ?render([ ?W2(?GRAY_BG, "No | Pid", 16), ?W2(?RED_BG, " Reductions", 15), ?W(?GRAY_BG, "Name|>Label|>Initial Call", 45), ?W(?GRAY_BG, " Memory", 13), ?W(?GRAY_BG, " MsgQueue", 10), ?W(?GRAY_BG, "Current Function", 33) ]), {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(ReductionList)), FormatFunc = fun(Item, {Acc, Acc1, Pos}) -> {Pid, Reductions, CurFun, NameOrCall} = get_top_n_info(Item), {Memory, MsgQueueLen} = get_pid_info(Pid, [memory, message_queue_len]), Format = get_reduction_format(ChoosePos, Pos), R = io_lib:format( Format, [ Pos, pid_to_list(Pid), observer_cli_lib:to_list(Reductions), NameOrCall, observer_cli_lib:to_byte(Memory), observer_cli_lib:to_list(MsgQueueLen), CurFun ] ), {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1} end, {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(ReductionList, Start, Num)), {PidList, [Title | lists:reverse(Rows)]}; render_top_n_view(total_heap_size, HeapList, Num, Pages, Page) -> Title = ?render([ ?W2(?GRAY_BG, "No | Pid", 16), ?W2(?RED_BG, " TotalHeapSize", 14), ?W(?GRAY_BG, "Name|>Label|>Initial Call", 45), ?W(?GRAY_BG, " Reductions", 14), ?W(?GRAY_BG, " MsgQueue", 10), ?W(?GRAY_BG, "Current Function", 32) ]), {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(HeapList)), FormatFunc = fun(Item, {Acc, Acc1, Pos}) -> {Pid, HeapSize, CurFun, NameOrCall} = get_top_n_info(Item), {Reductions, MsgQueueLen} = get_pid_info(Pid, [reductions, message_queue_len]), Format = get_memory_format(ChoosePos, Pos), R = io_lib:format( Format, [ Pos, pid_to_list(Pid), observer_cli_lib:to_byte(HeapSize), NameOrCall, observer_cli_lib:to_list(Reductions), observer_cli_lib:to_list(MsgQueueLen), CurFun ] ), {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1} end, {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(HeapList, Start, Num)), {PidList, [Title | lists:reverse(Rows)]}; render_top_n_view(message_queue_len, MQLenList, Num, Pages, Page) -> Title = ?render([ ?W2(?GRAY_BG, "No | Pid", 16), ?W2(?RED_BG, " MsgQueue", 11), ?W(?GRAY_BG, "Name|>Label|>Initial Call", 44), ?W(?GRAY_BG, " Memory", 13), ?W(?GRAY_BG, " Reductions", 14), ?W(?GRAY_BG, "Current Function", 33) ]), {Start, ChoosePos} = observer_cli_lib:get_pos(Page, Num, Pages, erlang:length(MQLenList)), FormatFunc = fun(Item, {Acc, Acc1, Pos}) -> {Pid, MQLen, CurFun, NameOrCall} = get_top_n_info(Item), {Reductions, Memory} = get_pid_info(Pid, [reductions, memory]), Format = get_message_queue_format(ChoosePos, Pos), R = io_lib:format( Format, [ Pos, pid_to_list(Pid), observer_cli_lib:to_list(MQLen), NameOrCall, observer_cli_lib:to_byte(Memory), observer_cli_lib:to_list(Reductions), CurFun ] ), {[R | Acc], [{Pos, Pid} | Acc1], Pos + 1} end, {Rows, PidList} = top_n_rows(FormatFunc, Start, lists:sublist(MQLenList, Start, Num)), {PidList, [Title | lists:reverse(Rows)]}. top_n_rows(FormatFunc, Start, List) -> {Row, PidList, _} = lists:foldl(FormatFunc, {[], [], Start}, List), {Row, PidList}. notify_pause_status() -> ?output("\e[31;1m PAUSE INPUT (p, r/rr, b/bb, h/hh, m/mm) to resume or q to quit \e[0m~n"). get_memory_format(Pos, Pos) -> "|\e[42m~-3.3w|~-12.12s|~13.13s |~-45.45s|~14.14s| ~-9.9s|~-33.33s\e[49m|~n"; get_memory_format(_Pos, _RankPos) -> "|~-3.3w|~-12.12s|~13.13s |~-45.45s|~14.14s| ~-9.9s|~-33.33s|~n". get_reduction_format(Pos, Pos) -> "|\e[42m~-3.3w|~-12.12s|~-15.15s|~-45.45s|~12.12s| ~-9.9s|~-34.34s\e[49m|~n"; get_reduction_format(_Pos, _RankPos) -> "|~-3.3w|~-12.12s|~-15.15s|~-45.45s|~12.12s| ~-9.9s|~-34.34s|~n". get_message_queue_format(Pos, Pos) -> "|\e[42m~-3.3w|~-12.12s|~-11.11s|~-44.44s|~13.13s| ~-13.13s|~-34.34s\e[49m|~n"; get_message_queue_format(_Pos, _RankPos) -> "|~-3.3w|~-12.12s|~-11.11s|~-44.44s|~13.13s| ~-13.13s|~-34.34s|~n". refresh_next_time(proc_count, Type, Interval) -> erlang:send_after(Interval, self(), {proc_count, Type}); refresh_next_time(proc_window, Type, _Interval) -> erlang:send_after(10, self(), {proc_window, Type}). get_current_initial_call(Call) -> {_, CurFun} = lists:keyfind(current_function, 1, Call), {_, InitialCall} = lists:keyfind(initial_call, 1, Call), {observer_cli_lib:mfa_to_list(CurFun), InitialCall}. get_port_proc_info(PortLimit, ProcLimit) -> ProcCount = erlang:system_info(process_count), PortCount = erlang:system_info(port_count), PortCountStr = [integer_to_list(PortCount), "/", integer_to_list(PortLimit)], ProcCountStr = [integer_to_list(ProcCount), "/", integer_to_list(ProcLimit)], PortWarning = case PortCount > PortLimit * ?COUNT_ALARM_THRESHOLD of true -> ?RED; false -> <<"">> end, ProcWarning = case ProcCount > ProcLimit * ?COUNT_ALARM_THRESHOLD of true -> ?RED; false -> <<"">> end, {PortWarning, ProcWarning, PortCountStr, ProcCountStr}. format_atom_info(AtomLimit, AtomCount) -> Atom = [integer_to_list(AtomCount), "/", integer_to_list(AtomLimit)], case AtomCount > AtomLimit * ?COUNT_ALARM_THRESHOLD of true -> {?RED, Atom}; false -> {<<"">>, Atom} end. warning_color(Percent) when Percent >= ?CPU_ALARM_THRESHOLD -> ?RED; warning_color(_Percent) -> ?GREEN. process_bar_format_style(Percents, IsLastLine) -> Format = case [warning_color(P) || P <- Percents] of [W1, W2] -> <<"|", W1/binary, "|~2..0w ~-57.57s~s", W2/binary, " |~2..0w ~-57.57s ~s \e[0m|~n">>; [W1, W2, W3, W4] -> <<"|", W1/binary, "|~-2.2w ~-22.22s ~s", W2/binary, " |~-2.2w ~-22.22s ~s", W3/binary, " |~-2.2w ~-22.22s ~s", W4/binary, " |~-2.2w ~-23.23s ~s \e[0m|~n">>; [W1, W2, W3, W4, W5, W6, W7, W8, W9, W10] -> <<"|", W1/binary, " | ~-3.3w ~s", W2/binary, " | ~-3.3w ~s", W3/binary, " | ~-3.3w ~s", W4/binary, " | ~-3.3w ~s", W5/binary, " | ~-3.3w ~s", W6/binary, " |=====| ~-3.3w ~s", W7/binary, " | ~-3.3w ~s", W8/binary, " | ~-3.3w ~s", W9/binary, " | ~-3.3w ~s", W10/binary, " | ~-3.3w ~s \e[0m|~n">> end, case IsLastLine of true -> <>; false -> Format end. get_top_n_info(Item) -> {Pid, Val, Call = [IsName | _]} = Item, {CurFun, InitialCall} = get_current_initial_call(Call), Flag = display_unique_flag(IsName, InitialCall, Pid), {Pid, Val, CurFun, Flag}. display_unique_flag(IsName, Call, Pid) -> case choose_name(IsName) of undefined -> case choose_label(Pid) of undefined -> choose_call(Call, Pid); Label -> Label end; Name -> Name end. choose_name(IsName) when is_atom(IsName) -> atom_to_list(IsName); choose_name(_) -> undefined. choose_label(Pid) -> case erlang:function_exported(proc_lib, get_label, 1) andalso proc_lib:get_label(Pid) of false -> undefined; undefined -> undefined; Label -> io_lib:format("~p", [Label]) end. choose_call({proc_lib, init_p, 5}, Pid) -> %% translate gen_xxx behavior observer_cli_lib:mfa_to_list(proc_lib:translate_initial_call(Pid)); choose_call(Call, _Pid) -> observer_cli_lib:mfa_to_list(Call). get_refresh_prompt(proc_count, Type, Interval, Rows) -> io_lib:format("recon:proc_count(~p, ~w) Interval:~wms", [Type, Rows, Interval]); get_refresh_prompt(proc_window, Type, Interval, Rows) -> io_lib:format("recon:proc_window(~p, ~w, ~w) Interval:~wms", [Type, Rows, Interval, Interval]). get_stable_system_info() -> OtpRelease = erlang:system_info(otp_release), SysVersion = erlang:system_info(system_version) -- "\n", { [ OtpRelease, SysVersion ++ " " ++ atom_to_list(erlang:node()), erlang:system_info(process_limit), erlang:system_info(port_limit), erlang:system_info(ets_limit) ], erlang:system_info(port_parallelism) }. get_atom_status() -> try erlang:system_info(atom_limit) of Limit -> Count = erlang:system_info(atom_count), {ok, Limit, Count} catch _:badarg -> {error, unsupported} end. get_pid_info(Pid, Keys) -> case recon:info(Pid, Keys) of undefined -> {"die", "die"}; [{_, Val1}, {_, Val2}] -> {Val1, Val2} end. get_top_n(proc_window, Type, Interval, Rows, IsFirstTime) when not IsFirstTime -> recon:proc_window(Type, Rows, Interval); get_top_n(_Func, Type, _Interval, Rows, _FirstTime) -> recon:proc_count(Type, Rows). connect_error(Prompt, Node) -> Prop = <>, ?output(Prop, [Node]). start_process_view(StorePid, RenderPid, Opts = #view_opts{home = Home}, LastSchWallFlag, AutoJump) -> #home{cur_page = CurPage, pages = Pages, scheduler_usage = SchUsage} = Home, {_, CurPos} = lists:keyfind(CurPage, 1, Pages), case observer_cli_store:lookup_pos(StorePid, CurPos) of {CurPos, ChoosePid} -> clean([RenderPid, StorePid, LastSchWallFlag, SchUsage]), observer_cli_process:start(home, ChoosePid, Opts); {_, ChoosePid} when AutoJump -> clean([RenderPid, StorePid, LastSchWallFlag, SchUsage]), observer_cli_process:start(home, ChoosePid, Opts); _ -> manager(StorePid, RenderPid, Opts, LastSchWallFlag) end. set_scheduler_wall_time(_Flag, ?DISABLE) -> false; set_scheduler_wall_time(Flag, ?ENABLE) -> erlang:system_flag(scheduler_wall_time, Flag). check_auto_row() -> case io:rows() of {ok, _} -> true; {error, _} -> false end. node_stats({LastIn, LastOut, LastGCs, LastWords, LastScheduleWall}, SchUsage) -> New = {In, Out, GCs, Words, ScheduleWall} = get_incremental_stats(SchUsage), BytesInDiff = In - LastIn, BytesOutDiff = Out - LastOut, GCCountDiff = GCs - LastGCs, GCWordsDiff = Words - LastWords, { { [observer_cli_lib:to_byte(In), "/", observer_cli_lib:to_byte(BytesInDiff)], [observer_cli_lib:to_byte(Out), "/", observer_cli_lib:to_byte(BytesOutDiff)], [integer_to_list(GCs), "/", integer_to_list(GCCountDiff)], [integer_to_list(Words), "/", integer_to_list(GCWordsDiff)] }, recon_lib:scheduler_usage_diff(LastScheduleWall, ScheduleWall), New }. get_incremental_stats(SchUsage) -> {{input, In}, {output, Out}} = erlang:statistics(io), {GCs, Words, _} = erlang:statistics(garbage_collection), ScheduleWall = case SchUsage of ?ENABLE -> erlang:statistics(scheduler_wall_time); ?DISABLE -> undefined end, {In, Out, GCs, Words, ScheduleWall}.