1%%% @author zhongwen <zhongwencool@gmail.com> 2-module(observer_cli_application). 3 4-include("observer_cli.hrl"). 5 6%% API 7-export([start/1]). 8-export([clean/1]). 9 10%% API 11-define(LAST_LINE, 12 "refresh: ~wms q(quit) Positive Number(set refresh interval time ms) F/B(forward/back) Current pages is ~w" 13). 14 15%% @doc List application info 16 17-spec start(ViewOpts) -> no_return when ViewOpts :: view_opts(). 18start(#view_opts{app = App, auto_row = AutoRow} = ViewOpts) -> 19 Pid = spawn_link(fun() -> 20 ?output(?CLEAR), 21 render_worker(App, AutoRow) 22 end), 23 manager(Pid, ViewOpts). 24 25-spec clean(list()) -> ok. 26clean(Pids) -> observer_cli_lib:exit_processes(Pids). 27 28%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29%%% Private 30%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 31manager(Pid, Opts = #view_opts{app = App = #app{cur_page = CurPage}}) -> 32 case observer_cli_lib:parse_cmd(Opts, ?MODULE, [Pid]) of 33 quit -> 34 erlang:unlink(Pid), 35 erlang:send(Pid, quit), 36 quit; 37 {func, proc_count, message_queue_len} -> 38 clean([Pid]), 39 start(Opts#view_opts{app = App#app{type = {message_queue_len, 4}}}); 40 {func, proc_count, reductions} -> 41 clean([Pid]), 42 start(Opts#view_opts{app = App#app{type = {reductions, 3}}}); 43 {func, proc_count, memory} -> 44 clean([Pid]), 45 start(Opts#view_opts{app = App#app{type = {memory, 2}}}); 46 pause_or_resume -> 47 clean([Pid]), 48 start(Opts#view_opts{app = App#app{type = {proc_count, 1}}}); 49 {new_interval, NewInterval} -> 50 clean([Pid]), 51 start(Opts#view_opts{app = App#app{interval = NewInterval}}); 52 page_down_top_n -> 53 NewPage = max(CurPage + 1, 1), 54 clean([Pid]), 55 start(Opts#view_opts{app = App#app{cur_page = NewPage}}); 56 page_up_top_n -> 57 NewPage = max(CurPage - 1, 1), 58 clean([Pid]), 59 start(Opts#view_opts{app = App#app{cur_page = NewPage}}); 60 _ -> 61 manager(Pid, Opts) 62 end. 63 64render_worker(App, AutoRow) -> 65 #app{type = Type, interval = Interval, cur_page = CurPage} = App, 66 TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow), 67 Rows = erlang:max(TerminalRow - 5, 0), 68 Text = "Interval: " ++ integer_to_list(Interval) ++ "ms", 69 Menu = observer_cli_lib:render_menu(app, Text), 70 Info = render_app_info(Rows, CurPage, Type), 71 LastText = io_lib:format(?LAST_LINE, [Interval, CurPage]), 72 LastLine = observer_cli_lib:render_last_line(LastText), 73 ?output([?CURSOR_TOP, Menu, Info, LastLine]), 74 erlang:send_after(Interval, self(), redraw), 75 receive 76 quit -> quit; 77 redraw -> render_worker(App, AutoRow) 78 end. 79 80render_app_info(Row, CurPage, {Type, N}) -> 81 List = [ 82 begin 83 {0, {element(N, I), S}, [App, C, M, R, Q, S, V]} 84 end 85 || {App, I = {C, M, R, Q, S, V}} <- maps:to_list(app_info()) 86 ], 87 {StartPos, SortList} = observer_cli_lib:sublist(List, Row, CurPage), 88 InitColor = [ 89 {memory, ?GRAY_BG}, 90 {proc_count, ?GRAY_BG}, 91 {reductions, ?GRAY_BG}, 92 {message_queue_len, ?GRAY_BG} 93 ], 94 [ 95 {_, MemColor}, 96 {_, ProcessColor}, 97 {_, RedColor}, 98 {_, MsgQColor} 99 ] = lists:keyreplace(Type, 1, InitColor, {Type, ?RED_BG}), 100 Title = ?render([ 101 ?UNDERLINE, 102 ?W2(?GRAY_BG, "Id", 3), 103 ?UNDERLINE, 104 ?W2(?GRAY_BG, "App", 31), 105 ?UNDERLINE, 106 ?W2(ProcessColor, "ProcessCount(p)", 20), 107 ?UNDERLINE, 108 ?W2(MemColor, "Memory(m)", 20), 109 ?UNDERLINE, 110 ?W2(RedColor, "Reductions(r)", 17), 111 ?UNDERLINE, 112 ?W2(MsgQColor, "MsgQ(mq)", 10), 113 ?UNDERLINE, 114 ?W2(?GRAY_BG, "Status", 12), 115 ?UNDERLINE, 116 ?W2(?GRAY_BG, "version", 16) 117 ]), 118 {_, View} = lists:foldl( 119 fun({_, _, Item}, {Pos, Acc}) -> 120 [App, C, R, M, Q, S, V] = Item, 121 {Pos + 1, [ 122 ?render([ 123 ?W(Pos, 2), 124 ?W(App, 29), 125 ?W(C, 18), 126 ?W({byte, M}, 18), 127 ?W(R, 15), 128 ?W(Q, 8), 129 ?W(S, 10), 130 ?W(V, 15) 131 ]) 132 | Acc 133 ]} 134 end, 135 {StartPos, []}, 136 SortList 137 ), 138 [Title | lists:reverse(View)]. 139 140app_info() -> 141 Info = application:info(), 142 AllApps = app_status(Info), 143 Leaders = leader_info(Info), 144 app_info(AllApps, Leaders, erlang:processes(), self()). 145 146app_info(AllApps, _Leaders, [], _Self) -> 147 AllApps; 148app_info(AllApps, Leaders, [Self | Process], Self) -> 149 app_info(AllApps, Leaders, Process, Self); 150app_info(AllApps, Leaders, [Pid | Process], Self) -> 151 case erlang:process_info(Pid, [group_leader, memory, reductions, message_queue_len]) of 152 undefined -> 153 app_info(AllApps, Leaders, Process, Self); 154 Prop -> 155 [ 156 {group_leader, Group}, 157 {memory, Memory}, 158 {reductions, Reds}, 159 {message_queue_len, MsgQ} 160 ] = Prop, 161 NewAllApps = 162 case maps:find(Group, Leaders) of 163 error -> 164 {ok, {C1, M1, R1, Q1, S1, V1}} = maps:find(unknown, AllApps), 165 NewInfo = {C1 + 1, M1 + Memory, R1 + Reds, Q1 + MsgQ, S1, V1}, 166 maps:put(unknown, NewInfo, AllApps); 167 {ok, App} -> 168 {ok, {C, M, R, Q, S, V}} = maps:find(App, AllApps), 169 maps:put(App, {C + 1, M + Memory, R + Reds, Q + MsgQ, S, V}, AllApps) 170 end, 171 app_info(NewAllApps, Leaders, Process, Self) 172 end. 173 174leader_info(Info) -> 175 {running, Running} = lists:keyfind(running, 1, Info), 176 leader_info(Running, #{}). 177 178leader_info([{App, Sup} | Running], Acc) when is_pid(Sup) -> 179 NewAcc = 180 case erlang:process_info(Sup, group_leader) of 181 undefined -> 182 Acc; 183 {group_leader, Pid} -> 184 Acc#{Pid => App} 185 end, 186 leader_info(Running, NewAcc); 187leader_info([_ | Running], Acc) -> 188 leader_info(Running, Acc); 189leader_info([], Acc) -> 190 Acc. 191 192app_status(Info) -> 193 {loaded, Loaded} = lists:keyfind(loaded, 1, Info), 194 {loading, Loading} = lists:keyfind(loading, 1, Info), 195 {started, Started} = lists:keyfind(started, 1, Info), 196 {start_p_false, StartPFalse} = lists:keyfind(start_p_false, 1, Info), 197 {starting, Starting} = lists:keyfind(starting, 1, Info), 198 R0 = #{unknown => {0, 0, 0, 0, "Unknown", "unknown"}}, 199 R1 = lists:foldl( 200 fun({App, _From}, Acc) -> 201 Acc#{App => {0, 0, 0, 0, "Loading", "unknown"}} 202 end, 203 R0, 204 Loading 205 ), 206 R2 = lists:foldl( 207 fun({App, _Desc, Version}, Acc) -> 208 Acc#{App => {0, 0, 0, 0, "Loaded", Version}} 209 end, 210 R1, 211 Loaded 212 ), 213 R3 = lists:foldl( 214 fun({App, _RestartType, _Type, _From}, Acc) -> 215 Version = get_version(App, Acc), 216 Acc#{App => {0, 0, 0, 0, "Starting", Version}} 217 end, 218 R2, 219 Starting 220 ), 221 R4 = lists:foldl( 222 fun({App, _RestartType}, Acc) -> 223 Version = get_version(App, Acc), 224 Acc#{App => {0, 0, 0, 0, "Started", Version}} 225 end, 226 R3, 227 Started 228 ), 229 lists:foldl( 230 fun({App, _RestartType, _Type, _From}, Acc) -> 231 Version = get_version(App, Acc), 232 Acc#{App => {0, 0, 0, 0, "StartPFlase", Version}} 233 end, 234 R4, 235 StartPFalse 236 ). 237 238get_version(App, Maps) -> 239 case maps:find(App, Maps) of 240 {ok, {_, _, _, _, _, V}} -> V; 241 _ -> "unknown" 242 end. 243