1%%% @author zhongwen <zhongwencool@gmail.com>
2-module(observer_cli_ets).
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) s(sort by size) m(sort by memory) pd/pu(page:down/up) F/B(forward/back)"
12).
13
14-spec start(ViewOpts) -> no_return() when ViewOpts :: view_opts().
15start(
16    #view_opts{
17        ets = #ets{interval = Interval, attr = Attr, cur_page = CurPage},
18        auto_row = AutoRow
19    } = ViewOpts
20) ->
21    Pid = spawn_link(fun() ->
22        ?output(?CLEAR),
23        render_worker(Interval, ?INIT_TIME_REF, Attr, CurPage, AutoRow)
24    end),
25    manager(Pid, ViewOpts).
26
27-spec clean(list()) -> ok.
28clean(Pids) -> observer_cli_lib:exit_processes(Pids).
29
30%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
31%%% Private
32%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33manager(ChildPid, #view_opts{ets = EtsOpts = #ets{cur_page = CurPage}} = ViewOpts) ->
34    case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [ChildPid]) of
35        quit ->
36            erlang:send(ChildPid, quit);
37        {new_interval, NewMs} ->
38            clean([ChildPid]),
39            start(ViewOpts#view_opts{ets = EtsOpts#ets{interval = NewMs}});
40        size ->
41            clean([ChildPid]),
42            start(ViewOpts#view_opts{ets = EtsOpts#ets{attr = size}});
43        %% Home
44        {func, proc_count, memory} ->
45            clean([ChildPid]),
46            start(ViewOpts#view_opts{ets = EtsOpts#ets{attr = memory}});
47        page_down_top_n ->
48            NewPage = max(CurPage + 1, 1),
49            clean([ChildPid]),
50            start(ViewOpts#view_opts{ets = EtsOpts#ets{cur_page = NewPage}});
51        page_up_top_n ->
52            NewPage = max(CurPage - 1, 1),
53            clean([ChildPid]),
54            start(ViewOpts#view_opts{ets = EtsOpts#ets{cur_page = NewPage}});
55        _ ->
56            manager(ChildPid, ViewOpts)
57    end.
58
59render_worker(Interval, LastTimeRef, Attr, CurPage, AutoRow) ->
60    TerminalRow = observer_cli_lib:get_terminal_rows(AutoRow),
61    Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
62    Menu = observer_cli_lib:render_menu(ets, Text),
63    Ets = render_ets_info(erlang:max(0, TerminalRow - 4), CurPage, Attr),
64    LastLine = observer_cli_lib:render_last_line(?LAST_LINE),
65    ?output([?CURSOR_TOP, Menu, Ets, LastLine]),
66    NextTimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval),
67    receive
68        quit -> quit;
69        _ -> render_worker(Interval, NextTimeRef, Attr, CurPage, AutoRow)
70    end.
71
72render_ets_info(Rows, CurPage, Attr) ->
73    AllEts = [
74        begin
75            get_ets_info(Tab, Attr)
76        end
77        || Tab <- ets:all()
78    ],
79    WordSize = erlang:system_info(wordsize),
80    {_StartPos, SortEts} = observer_cli_lib:sublist(AllEts, Rows, CurPage),
81    {MemColor, SizeColor} =
82        case Attr of
83            memory -> {?RED_BG, ?GRAY_BG};
84            _ -> {?GRAY_BG, ?RED_BG}
85        end,
86    Title = ?render([
87        ?UNDERLINE,
88        ?W2(?GRAY_BG, "Table Name", 37),
89        ?UNDERLINE,
90        ?W2(SizeColor, "Size", 14),
91        ?UNDERLINE,
92        ?W2(MemColor, "    Memory    ", 14),
93        ?UNDERLINE,
94        ?W2(?GRAY_BG, "Type", 15),
95        ?UNDERLINE,
96        ?W2(?GRAY_BG, "Protection", 12),
97        ?UNDERLINE,
98        ?W2(?GRAY_BG, "KeyPos", 8),
99        ?UNDERLINE,
100        ?W2(?GRAY_BG, "Write/Read", 14),
101        ?UNDERLINE,
102        ?W2(?GRAY_BG, "Owner Pid", 15)
103    ]),
104    RowView = [
105        begin
106            Name = proplists:get_value(name, Ets),
107            Memory = proplists:get_value(memory, Ets),
108            Size = proplists:get_value(size, Ets),
109            Type = proplists:get_value(type, Ets),
110            Protect = proplists:get_value(protection, Ets),
111            KeyPos = proplists:get_value(keypos, Ets),
112            Write = observer_cli_lib:to_list(proplists:get_value(write_concurrency, Ets)),
113            Read = observer_cli_lib:to_list(proplists:get_value(read_concurrency, Ets)),
114            Owner = proplists:get_value(owner, Ets),
115            ?render([
116                ?W(Name, 36),
117                ?W(Size, 12),
118                ?W({byte, Memory * WordSize}, 12),
119                ?W(Type, 13),
120                ?W(Protect, 10),
121                ?W(KeyPos, 6),
122                ?W(Write ++ "/" ++ Read, 12),
123                ?W(Owner, 14)
124            ])
125        end
126        || {_, _, Ets} <- SortEts
127    ],
128    [Title | RowView].
129
130get_ets_info(Tab, Attr) ->
131    case catch ets:info(Tab) of
132        {'EXIT', _} ->
133            {
134                0,
135                0,
136                [
137                    %%it maybe die
138                    {name, unread},
139                    {write_concurrency, unread},
140                    {read_concurrency, unread},
141                    {compressed, unread},
142                    {memory, unread},
143                    {owner, unread},
144                    {heir, unread},
145                    {size, unread},
146                    {node, unread},
147                    {named_table, unread},
148                    {type, unread},
149                    {keypos, unread},
150                    {protection, unread}
151                ]
152            };
153        Info when is_list(Info) ->
154            Owner = proplists:get_value(owner, Info),
155            NewInfo =
156                case is_reg(Owner) of
157                    Owner -> Info;
158                    Reg -> lists:keyreplace(Owner, 1, Info, {owner, Reg})
159                end,
160            {
161                0,
162                proplists:get_value(Attr, NewInfo),
163                NewInfo
164            }
165    end.
166
167is_reg(Owner) ->
168    case process_info(Owner, registered_name) of
169        {registered_name, Name} -> Name;
170        _ -> Owner
171    end.
172