-module(observer_cli_process). -include("observer_cli.hrl"). -export([start/3]). %% lists:foldl(fun(_X, Acc) -> queue:in('NaN', Acc) end, queue:new(), lists:seq(1, 5)) -define(INIT_QUEUE, {['NaN', 'NaN', 'NaN', 'NaN'], ['NaN']}). -spec start(Type, pid(), view_opts()) -> no_return when Type :: home | plugin. start(Type, Pid, Opts) -> #view_opts{process = #process{interval = RefreshMs}} = Opts, RenderPid = spawn_link(fun() -> ?output(?CLEAR), render_worker(info, Type, RefreshMs, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE) end), manager(RenderPid, Type, Opts). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Private %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% manager(RenderPid, Type, #view_opts{process = ProcOpts} = Opts) -> case parse_cmd() of quit -> erlang:send(RenderPid, quit); {new_interval, NewInterval} -> erlang:send(RenderPid, {new_interval, NewInterval}), NewOpt = Opts#view_opts{process = ProcOpts#process{interval = NewInterval}}, manager(RenderPid, Type, NewOpt); home -> erlang:exit(RenderPid, stop), observer_cli:start(Opts); back when Type =:= home -> erlang:exit(RenderPid, stop), observer_cli:start(Opts); back when Type =:= plugin -> erlang:exit(RenderPid, stop), observer_cli_plugin:start(Opts); ViewAction -> erlang:send(RenderPid, ViewAction), manager(RenderPid, Type, Opts) end. render_worker(info, Type, Interval, Pid, TimeRef, RedQ, MemQ) -> ProcessInfo = recon:info(Pid), Meta = proplists:get_value(meta, ProcessInfo), case Meta of undefined -> output_die_view(Pid, Type, Interval), next_draw_view(info, Type, TimeRef, Interval, Pid, RedQ, MemQ); _ -> RegisteredName = proplists:get_value(registered_name, Meta), GroupLeader = proplists:get_value(group_leader, Meta), Status = proplists:get_value(status, Meta), Signals = proplists:get_value(signals, ProcessInfo), Link = proplists:get_value(links, Signals), Monitors = proplists:get_value(monitors, Signals), MonitoredBy = proplists:get_value(monitored_by, Signals), TrapExit = proplists:get_value(trap_exit, Signals), Location = proplists:get_value(location, ProcessInfo), InitialCall = proplists:get_value(initial_call, Location), MemoryUsed = proplists:get_value(memory_used, ProcessInfo), Memory = proplists:get_value(memory, MemoryUsed), MessageQueueLen = proplists:get_value(message_queue_len, MemoryUsed), HeapSize = proplists:get_value(heap_size, MemoryUsed), TotalHeapSize = proplists:get_value(total_heap_size, MemoryUsed), GarbageCollection = proplists:get_value(garbage_collection, MemoryUsed), Work = proplists:get_value(work, ProcessInfo), Reductions = proplists:get_value(reductions, Work), Menu = render_menu(info, Type, Interval), Line1 = render_process_info( Pid, RegisteredName, GroupLeader, Status, TrapExit, InitialCall, MessageQueueLen, HeapSize, TotalHeapSize, GarbageCollection ), Line2 = render_link_monitor(Link, Monitors, MonitoredBy), {NewRedQ, NewMemQ, Line3} = render_reduction_memory(Reductions, Memory, RedQ, MemQ), LastLine = render_last_line(), ?output([?CURSOR_TOP, Menu, Line1, Line2, Line3, LastLine]), next_draw_view(info, Type, TimeRef, Interval, Pid, NewRedQ, NewMemQ) end; render_worker(message, Type, Interval, Pid, TimeRef, RedQ, MemQ) -> case erlang:process_info(Pid, message_queue_len) of {message_queue_len, Len} -> Line = if Len =:= 0 -> "\e[32;1mNo messages were found.\e[0m\n"; Len > 10000 -> io_lib:format("\e[31mToo many message(~w)!\e[0m~n", [Len]); true -> {messages, Messages} = recon:info(Pid, messages), [ io_lib:format("~p Message Len:~p~n", [Pid, Len]), truncate_str(Messages) ] end, Menu = render_menu(message, Type, Interval), LastLine = render_last_line(), ?output([?CURSOR_TOP, Menu, Line, LastLine]), next_draw_view(message, Type, TimeRef, Interval, Pid, RedQ, MemQ); undefined -> render_worker(info, Type, Interval, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE) end; render_worker(dict, Type, Interval, Pid, TimeRef, RedQ, MemQ) -> case erlang:process_info(Pid, dictionary) of {dictionary, List} -> Len = erlang:length(List), Line1 = io_lib:format( "erlang:process_info(~p, dictionary). dictionary_len:~p ~n", [Pid, Len] ), Line2 = case Len of 0 -> "\e[32;1mNo dictionary was found\e[0m\n"; _ -> truncate_str(List) end, Menu = render_menu(dict, Type, Interval), LastLine = render_last_line(), ?output([?CURSOR_TOP, Menu, Line1, Line2, LastLine]), next_draw_view(dict, Type, TimeRef, Interval, Pid, RedQ, MemQ); undefined -> render_worker(info, Type, Interval, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE) end; render_worker(stack, Type, Interval, Pid, TimeRef, RedQ, MemQ) -> case erlang:process_info(Pid, current_stacktrace) of {current_stacktrace, StackTrace} -> Menu = render_menu(stack, Type, Interval), Prompt = io_lib:format("erlang:process_info(~p, current_stacktrace). ~n", [Pid]), LastLine = render_last_line(), {_, Line} = lists:foldr( fun({Mod, Func, Arity, Location}, {Nth, Acc}) -> Mfa = observer_cli_lib:mfa_to_list({Mod, Func, Arity}), File = proplists:get_value(file, Location, "undefined"), Line = proplists:get_value(line, Location, 0), FileLine = File ++ ":" ++ erlang:integer_to_list(Line), case Nth =:= 1 of false -> {Nth + 1, [?W(Mfa, 66), ?W(FileLine, 62), ?NEW_LINE | Acc]}; true -> {Nth + 1, [?W(Mfa, 66), ?W(FileLine, 62) | Acc]} end end, {1, []}, lists:sublist(StackTrace, 30) ), ?output([?CURSOR_TOP, Menu, Prompt, ?render(Line), LastLine]), next_draw_view(stack, Type, TimeRef, Interval, Pid, RedQ, MemQ); undefined -> render_worker(info, Type, Interval, Pid, ?INIT_TIME_REF, ?INIT_QUEUE, ?INIT_QUEUE) end; render_worker(state, Type, Interval, Pid, TimeRef, RedQ, MemQ) -> case render_state(Pid, Type, Interval) of ok -> next_draw_view(state, Type, TimeRef, Interval, Pid, RedQ, MemQ); error -> next_draw_view_2(state, Type, TimeRef, Interval, Pid, RedQ, MemQ) end. next_draw_view(Status, Type, TimeRef, Interval, Pid, NewRedQ, NewMemQ) -> NewTimeRef = observer_cli_lib:next_redraw(TimeRef, Interval), next_draw_view_2(Status, Type, NewTimeRef, Interval, Pid, NewRedQ, NewMemQ). next_draw_view_2(Status, Type, TimeRef, Interval, Pid, NewRedQ, NewMemQ) -> receive quit -> quit; {new_interval, NewInterval} -> ?output(?CLEAR), render_worker(Status, Type, NewInterval, Pid, TimeRef, NewRedQ, NewMemQ); info_view -> ?output(?CLEAR), render_worker(info, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ); message_view -> ?output(?CLEAR), render_worker(message, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ); dict_view -> ?output(?CLEAR), render_worker(dict, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ); stack_view -> ?output(?CLEAR), render_worker(stack, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ); state_view -> ?output(?CLEAR), render_worker(state, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ); _Msg -> render_worker(Status, Type, Interval, Pid, TimeRef, NewRedQ, NewMemQ) end. render_process_info( Pid, RegisteredName, GroupLeader, Status, TrapExit, InitialCall, MessageQueueLen, HeapSize, TotalHeapSize, GarbageCollection ) -> MinBinVHeapSize = proplists:get_value(min_bin_vheap_size, GarbageCollection), MinHeapSize = proplists:get_value(min_heap_size, GarbageCollection), FullSweepAfter = proplists:get_value(fullsweep_after, GarbageCollection), MinorGcs = integer_to_list(proplists:get_value(minor_gcs, GarbageCollection)), InitialCallStr = observer_cli_lib:mfa_to_list(InitialCall), GroupLeaderStr = erlang:pid_to_list(GroupLeader), PidStr = erlang:pid_to_list(Pid), Name = case RegisteredName of "" -> PidStr; _ -> PidStr ++ "/" ++ erlang:atom_to_list(RegisteredName) end, MessageQueueLenStr = erlang:integer_to_list(MessageQueueLen), MessageQueueLenColor = case MessageQueueLen > 0 of true -> ?RED; false -> ?GREEN end, [ ?render([ ?GRAY_BG, ?W("Meta", 16), ?W("Value", 42), ?W("Memory Used", 16), ?W("Value", 12), ?W("Garbage Collection", 18), ?W("Value", 12) ]), ?render([ ?W("registered_name", 16), ?W(Name, 42), ?W("msg_queue_len", 16), ?W2(MessageQueueLenColor, MessageQueueLenStr, 13), ?W(" min_bin_vheap_size", 19), ?W({byte, MinBinVHeapSize}, 12), ?NEW_LINE, ?W("initial_call", 16), ?W(InitialCallStr, 42), ?W("heap_size", 16), ?W({byte, HeapSize}, 12), ?W("min_heap_size", 18), ?W({byte, MinHeapSize}, 12), ?NEW_LINE, ?W("group_leader", 16), ?W(GroupLeaderStr, 42), ?W("total_heap_size", 16), ?W({byte, TotalHeapSize}, 12), ?W("fullsweep_after", 18), ?W({byte, FullSweepAfter}, 12), ?NEW_LINE, ?W("status", 16), ?W(Status, 42), ?W("trap_exit", 16), ?W(TrapExit, 12), ?W("minor_gcs", 18), ?W(MinorGcs, 12) ]) ]. render_link_monitor(Link, Monitors, MonitoredBy) -> LinkStr = [ begin observer_cli_lib:to_list(P) end || P <- lists:sublist(Link, 30) ], MonitorsStr = [ begin case P of {process, Pid} -> observer_cli_lib:to_list(Pid); {RegName, Node} -> observer_cli_lib:to_list(RegName) ++ "/" ++ observer_cli_lib:to_list(Node) end end || P <- lists:sublist(Monitors, 30) ], MonitoredByStr = [ begin observer_cli_lib:to_list(P) end || P <- lists:sublist(MonitoredBy, 30) ], LinkInfo = "Links(" ++ erlang:integer_to_list(erlang:length(Link)) ++ ")", MonitorInfo = "Monitors(" ++ erlang:integer_to_list(erlang:length(Monitors)) ++ ")", MonitoredByInfo = "MonitoredBy(" ++ erlang:integer_to_list(erlang:length(MonitoredBy)) ++ ")", ?render([ ?W(LinkInfo, 16), ?W(LinkStr, 112), ?NEW_LINE, ?W(MonitorInfo, 16), ?W(MonitorsStr, 112), ?NEW_LINE, ?W(MonitoredByInfo, 16), ?W(MonitoredByStr, 112) ]). render_reduction_memory(Reduction, Memory, ReductionQ, MemoryQ) -> {NewRed, NewMem} = case queue:len(ReductionQ) >= 20 of true -> RestRed = queue:tail(ReductionQ), RestMem = queue:tail(MemoryQ), {queue:in(Reduction, RestRed), queue:in(Memory, RestMem)}; false -> {queue:in(Reduction, ReductionQ), queue:in(Memory, MemoryQ)} end, View = [ io_lib:format("|Reductions: ~120.120s|~n", [get_chart_format(NewRed)]), io_lib:format("|Memorys: ~123.123s|~n", [get_chart_format(NewMem)]) ], {NewRed, NewMem, View}. render_last_line() -> io_lib:format("|\e[7mq(quit) ~124.124s\e[0m|~n", [" "]). get_chart_format(Queue) -> List = queue:to_list(Queue), chart_format(List, ""). chart_format([_R], Lines) -> Lines; chart_format([R, R | RestRed], Lines) -> chart_format([R | RestRed], Lines ++ observer_cli_lib:to_list(R) ++ "->"); chart_format([R1, R2 | RestRed], Lines) when R1 > R2 -> chart_format([R2 | RestRed], Lines ++ observer_cli_lib:to_list(R1) ++ "->"); chart_format([R1, R2 | RestRed], Lines) when R1 < R2 -> chart_format([R2 | RestRed], Lines ++ observer_cli_lib:to_list(R1) ++ "->"). render_menu(Type, Menu, Interval) -> Text = "Interval: " ++ integer_to_list(Interval) ++ "ms", Title = get_menu_title(Type, Menu), UpTime = observer_cli_lib:uptime(), TitleWidth = ?COLUMN + 104 - erlang:length(UpTime), ?render([?W([Title | Text], TitleWidth) | UpTime]). get_menu_title(Type, Menu) -> MenuStr = case Menu of home -> "Home(H)"; plugin -> "Back(B)" end, [Home, Process, Messages, Dict, Stack, State] = get_menu_title2(Type, MenuStr), [Home, "|", Process, "|", Messages, "|", Dict, "|", Stack, "|", State, "|"]. get_menu_title2(info, Menu) -> [ ?UNSELECT(Menu), ?SELECT("Process Info(P)"), ?UNSELECT("Messages(M)"), ?UNSELECT("Dictionary(D)"), ?UNSELECT("Current Stack(C)"), ?UNSELECT("State(S)") ]; get_menu_title2(message, Menu) -> [ ?UNSELECT(Menu), ?UNSELECT("Process Info(P)"), ?SELECT("Messages(M)"), ?UNSELECT("Dictionary(D)"), ?UNSELECT("Current Stack(C)"), ?UNSELECT("State(S)") ]; get_menu_title2(dict, Menu) -> [ ?UNSELECT(Menu), ?UNSELECT("Process Info(P)"), ?UNSELECT("Messages(M)"), ?SELECT("Dictionary(D)"), ?UNSELECT("Current Stack(C)"), ?UNSELECT("State(S)") ]; get_menu_title2(stack, Menu) -> [ ?UNSELECT(Menu), ?UNSELECT("Process Info(P)"), ?UNSELECT("Messages(M)"), ?UNSELECT("Dictionary(D)"), ?SELECT("Current Stack(C)"), ?UNSELECT("State(S)") ]; get_menu_title2(state, Menu) -> [ ?UNSELECT(Menu), ?UNSELECT("Process Info(P)"), ?UNSELECT("Messages(M)"), ?UNSELECT("Dictionary(D)"), ?UNSELECT("Current Stack(C)"), ?SELECT("State(S)") ]. parse_cmd() -> case observer_cli_lib:to_list(io:get_line("")) of "q\n" -> quit; "Q\n" -> quit; "P\n" -> info_view; "M\n" -> message_view; "D\n" -> dict_view; "C\n" -> stack_view; "S\n" -> state_view; "H\n" -> home; "B\n" -> back; %% {error, estale}|{error, terminated} {error, _Reason} -> quit; Number -> observer_cli_lib:parse_integer(Number) end. render_state(Pid, Type, Interval) -> Menu = render_menu(state, Type, Interval), PromptRes = io_lib:format("recon:get_state(~p, 2500). ~n", [Pid]), PromptBefore = io_lib:format("\e[32;1mWaiting recon:get_state(~p, 2500) return...\e[0m~n", [Pid]), LastLine = render_last_line(), ?output([?CURSOR_TOP, Menu, PromptBefore]), try State = recon:get_state(Pid, 2500), Line = truncate_str(State), ?output([?CURSOR_TOP, Menu, PromptRes, Line, LastLine]), ok catch _Err:_Reason -> Error = "Information could not be retrieved, system messages may not be handled by this process.\n", ?output([?CURSOR_TOP, Menu, PromptRes, Error, LastLine]), error end. output_die_view(Pid, Type, Interval) -> Menu = render_menu(info, Type, Interval), Line = io_lib:format("\e[31mProcess(~p) has already die.\e[0m~n", [Pid]), LastLine = render_last_line(), ?output([?CURSOR_TOP, Menu, Line, LastLine]). truncate_str(Input) -> lists:sublist(lists:flatten(io_lib:format("~W", [Input, 50 * 30])), 140 * 30) ++ "\n".