diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml
index e1005ed..b5a945f 100644
--- a/.github/workflows/erlang.yml
+++ b/.github/workflows/erlang.yml
@@ -20,4 +20,6 @@ jobs:
run: rebar3 compile
- name: xref
run: rebar3 xref
+ - name: dialyzer
+ run: rebar3 dialyzer
diff --git a/README.md b/README.md
index 55c0e71..d93faec 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ Visualize Erlang/Elixir Nodes On The Command Line base on [recon](https://github
%% rebar.config
{deps, [observer_cli]}
%% erlang.mk
-dep_observer_cli = hex 1.7.5
+dep_observer_cli = hex 1.8.0
```
**Elixir**
@@ -35,7 +35,7 @@ dep_observer_cli = hex 1.7.5
```elixir
# mix.exs
def deps do
- [{:observer_cli, "~> 1.7"}]
+ [{:observer_cli, "~> 1.8"}]
end
```
@@ -89,7 +89,7 @@ _build/dev/rel/example/bin/example rpc ":observer_cli.start"
### DEMO
-
+
### How to write your own plugin?
@@ -255,7 +255,12 @@ Support F/B to page up/down.
---
### Changelog
-
+- 1.8.0
+ - Support ` 2.5.1"},
+ {:recon, "~> 2.5.6"},
]
]
end
diff --git a/mix.lock b/mix.lock
index 7636f67..da4116d 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,3 +1,3 @@
%{
- "recon": {:hex, :recon, "2.5.1", "430ffa60685ac1efdfb1fe4c97b8767c92d0d92e6e7c3e8621559ba77598678a", [:mix, :rebar3], [], "hexpm"},
+ "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
}
diff --git a/rebar.config b/rebar.config
index 517203e..3f7fa9a 100644
--- a/rebar.config
+++ b/rebar.config
@@ -18,18 +18,12 @@
]}.
{deps_dir, "deps"}.
{deps, [
- recon,
- {eqwalizer_support,
- {git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"},
- "eqwalizer_support"}}
+ recon
]}.
{project_plugins, [
rebar3_format,
- erlfmt,
- {eqwalizer_rebar3,
- {git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"},
- "eqwalizer_rebar3"}}
+ erlfmt
]}.
{escript_main_app, observer_cli}.
diff --git a/rebar.lock b/rebar.lock
index 2621c54..91585f1 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -1,13 +1,8 @@
{"1.2.0",
-[{<<"eqwalizer_support">>,
- {git_subdir,"https://github.com/whatsapp/eqwalizer.git",
- {ref,"0c099bbfa2fba1869d60e239c9b2cf2b6fb31ea9"},
- "eqwalizer_support"},
- 0},
- {<<"recon">>,{pkg,<<"recon">>,<<"2.5.5">>},0}]}.
+[{<<"recon">>,{pkg,<<"recon">>,<<"2.5.6">>},0}]}.
[
{pkg_hash,[
- {<<"recon">>, <<"C108A4C406FA301A529151A3BB53158CADC4064EC0C5F99B03DDB8C0E4281BDF">>}]},
+ {<<"recon">>, <<"9052588E83BFEDFD9B72E1034532AEE2A5369D9D9343B61AEB7FBCE761010741">>}]},
{pkg_hash_ext,[
- {<<"recon">>, <<"632A6F447DF7CCC1A4A10BDCFCE71514412B16660FE59DECA0FCF0AA3C054404">>}]}
+ {<<"recon">>, <<"96C6799792D735CC0F0FD0F86267E9D351E63339CBE03DF9D162010CEFC26BB0">>}]}
].
diff --git a/src/observer_cli.app.src b/src/observer_cli.app.src
index 406c247..fe17530 100644
--- a/src/observer_cli.app.src
+++ b/src/observer_cli.app.src
@@ -1,39 +1,39 @@
-{application, observer_cli,
- [
- {description, "Visualize Erlang Nodes On The Command Line"},
- {vsn, "1.7.5"},
- {modules, [
- observer_cli
- ]},
- {registered, []},
- {applications, [
- kernel,
- stdlib,
- recon
- ]},
- {files, ["include",
- "LICENSE*",
- "mix.exs",
- "mix.lock",
- "README.md",
- "rebar.config",
- "rebar.lock",
- "src"]},
- {build_tools, ["mix", "rebar3"]},
- {env, [
- {scheduler_usage, disable},
- {plugins,
- [
- %% module - Specific module implements plugin behavior. It's mandatory.
- %% title - Menu title. It's mandatory.
- %% shortcut - Switch plugin by shortcut. It's mandatory.
- %% interval - Refresh interval ms. It's options. default is 1500ms.
- %% sort_column - Sort the sheet by this index. It's options default is 2.
-
- %% #{module => observer_cli_plug_1, title => "Example-1", interval => 1500, shortcut => "S", sort_column => 3},
- %% #{module => observer_cli_plug_2, title => "Example-2", interval => 1600, shortcut => "D", sort_column => 2}
- ]}
- ]},
- {licenses, ["MIT"]},
- {links, [{"Github", "https://github.com/zhongwencool/observer_cli"}]}
- ]}.
+{application, observer_cli, [
+ {description, "Visualize Erlang Nodes On The Command Line"},
+ {vsn, "1.8.0"},
+ {modules, [
+ observer_cli
+ ]},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib,
+ recon
+ ]},
+ {files, [
+ "include",
+ "LICENSE*",
+ "mix.exs",
+ "mix.lock",
+ "README.md",
+ "rebar.config",
+ "rebar.lock",
+ "src"
+ ]},
+ {build_tools, ["mix", "rebar3"]},
+ {env, [
+ {scheduler_usage, disable},
+ {plugins, [
+ %% module - Specific module implements plugin behavior. It's mandatory.
+ %% title - Menu title. It's mandatory.
+ %% shortcut - Switch plugin by shortcut. It's mandatory.
+ %% interval - Refresh interval ms. It's options. default is 1500ms.
+ %% sort_column - Sort the sheet by this index. It's options default is 2.
+
+ %% #{module => observer_cli_plug_1, title => "Example-1", interval => 1500, shortcut => "S", sort_column => 3},
+ %% #{module => observer_cli_plug_2, title => "Example-2", interval => 1600, shortcut => "D", sort_column => 2}
+ ]}
+ ]},
+ {licenses, ["MIT"]},
+ {links, [{"Github", "https://github.com/zhongwencool/observer_cli"}]}
+]}.
diff --git a/src/observer_cli.erl b/src/observer_cli.erl
index c1f3e40..c47d404 100644
--- a/src/observer_cli.erl
+++ b/src/observer_cli.erl
@@ -23,7 +23,8 @@
-spec start() -> no_return | {badrpc, term()}.
start() -> start(#view_opts{}).
--spec start(Node) -> no_return | {badrpc, term()} when Node :: atom() | non_neg_integer().
+-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) ->
@@ -138,6 +139,9 @@ manager(StorePid, RenderPid, Opts, LastSchWallFlag) ->
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.
@@ -289,6 +293,7 @@ render_memory_process_line(MemSum, PortParallelism, Interval) ->
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,
@@ -349,7 +354,7 @@ render_memory_process_line(MemSum, PortParallelism, Interval) ->
?W("Gc Count", 20),
?W(GcCount, 24),
?NEW_LINE,
- ?W("Ets", 10),
+ ?W("Ets/" ++ erlang:integer_to_list(EtsLen), 10),
?W({byte, EtsMem}, 12),
?W(EtsMemPercent, 6),
?W(LogKey, 25),
@@ -777,6 +782,8 @@ choose_name(IsName) when is_atom(IsName) ->
choose_name(_) ->
undefined.
+%% proc_lib:get_label/1 is not exported before OTP 27
+-dialyzer([{nowarn_function, [choose_lable/1]}]).
choose_lable(Pid) ->
case
erlang:function_exported(proc_lib, get_label, 1) andalso
diff --git a/src/observer_cli_application.erl b/src/observer_cli_application.erl
index ff4d9a4..e03c79e 100644
--- a/src/observer_cli_application.erl
+++ b/src/observer_cli_application.erl
@@ -14,7 +14,7 @@
%% @doc List application info
--spec start(ViewOpts) -> no_return when ViewOpts :: view_opts().
+-spec start(ViewOpts) -> quit when ViewOpts :: view_opts().
start(#view_opts{app = App, auto_row = AutoRow} = ViewOpts) ->
Pid = spawn_link(fun() ->
?output(?CLEAR),
diff --git a/src/observer_cli_help.erl b/src/observer_cli_help.erl
index 6c218d6..b15f085 100644
--- a/src/observer_cli_help.erl
+++ b/src/observer_cli_help.erl
@@ -38,17 +38,21 @@ render_worker(Interval) ->
Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
Menu = observer_cli_lib:render_menu(doc, Text),
?output([?CURSOR_TOP, Menu]),
+ render_doc(Text),
erlang:send_after(Interval, self(), redraw),
receive
redraw ->
render_worker(Interval);
quit ->
- MenuQ = observer_cli_lib:render_menu(doc, Text),
- HelpQ = render_help(),
- LastLine = observer_cli_lib:render_last_line("q(quit)"),
- ?output([?CURSOR_TOP, MenuQ, HelpQ, ?UNDERLINE, ?GRAY_BG, LastLine, ?RESET_BG, ?RESET])
+ ok
end.
+render_doc(Text) ->
+ MenuQ = observer_cli_lib:render_menu(doc, Text),
+ HelpQ = render_help(),
+ LastLine = observer_cli_lib:render_last_line("q(quit)"),
+ ?output([?CURSOR_TOP, MenuQ, HelpQ, ?UNDERLINE, ?GRAY_BG, LastLine, ?RESET_BG, ?RESET]).
+
render_help() ->
[
"|\e[44m1. Start Mode\e[49m \n",
@@ -57,22 +61,24 @@ render_help() ->
"| \e[48;2;80;80;80m1.3\e[0m observer_start:start(Node, Cookie).\n",
"|\e[44m2. HOME(H) Commands\e[49m \n",
- "| \e[48;2;80;80;80m` \e[0m enable/disable schedule usage.\n",
- "| \e[48;2;80;80;80mPageDown \e[0m pd or F(forward).\n",
- "| \e[48;2;80;80;80mPageUp \e[0m pu or B(back).\n",
- "| \e[48;2;80;80;80mr \e[0m switch mode to reduction(proc_count).\n",
- "| \e[48;2;80;80;80mrr \e[0m switch mode to reduction(proc_window).\n",
- "| \e[48;2;80;80;80mm \e[0m switch mode to memory(proc_count).\n",
- "| \e[48;2;80;80;80mmm \e[0m switch mode to memory(proc_window).\n",
- "| \e[48;2;80;80;80mb \e[0m switch mode to bin memory(proc_count).\n",
- "| \e[48;2;80;80;80mbb \e[0m switch mode to bin memory(proc_window).\n",
- "| \e[48;2;80;80;80mmq \e[0m switch mode to message queue len(proc_count).\n",
- "| \e[48;2;80;80;80mmmq \e[0m switch mode to message queue len(proc_window).\n",
- "| \e[48;2;80;80;80mt \e[0m switch mode to total heap size(proc_count).\n",
- "| \e[48;2;80;80;80mtt \e[0m switch mode to total heap size(proc_window).\n",
- "| \e[48;2;80;80;80m3000 \e[0m set interval time to 3000ms, the integer must >= 1500.\n",
- "| \e[48;2;80;80;80m13 \e[0m choose the 13th process(green line), the integer must in top list.\n",
- "| \e[48;2;80;80;80mp \e[0m pause/unpause the view.\n",
+ "| \e[48;2;80;80;80m` \e[0m enable/disable schedule usage.\n",
+ "| \e[48;2;80;80;80mPageDown \e[0m pd or F(forward).\n",
+ "| \e[48;2;80;80;80mPageUp \e[0m pu or B(back).\n",
+ "| \e[48;2;80;80;80mr \e[0m switch mode to reduction(proc_count).\n",
+ "| \e[48;2;80;80;80mrr \e[0m switch mode to reduction(proc_window).\n",
+ "| \e[48;2;80;80;80mm \e[0m switch mode to memory(proc_count).\n",
+ "| \e[48;2;80;80;80mmm \e[0m switch mode to memory(proc_window).\n",
+ "| \e[48;2;80;80;80mb \e[0m switch mode to bin memory(proc_count).\n",
+ "| \e[48;2;80;80;80mbb \e[0m switch mode to bin memory(proc_window).\n",
+ "| \e[48;2;80;80;80mmq \e[0m switch mode to message queue len(proc_count).\n",
+ "| \e[48;2;80;80;80mmmq \e[0m switch mode to message queue len(proc_window).\n",
+ "| \e[48;2;80;80;80mt \e[0m switch mode to total heap size(proc_count).\n",
+ "| \e[48;2;80;80;80mtt \e[0m switch mode to total heap size(proc_window).\n",
+ "| \e[48;2;80;80;80m3000 \e[0m set interval time to 3000ms, the integer must >= 1500.\n",
+ "| \e[48;2;80;80;80m13 \e[0m choose the 13th process(green line), the integer must in top list.\n",
+ "| \e[48;2;80;80;80m<0.43.0> \e[0m choose the <0.43.0> process, the pid does not need to be in the top list.\n",
+ "| \e[48;2;80;80;80m<431 or >431 \e[0m choose the <0.431.0> process, the pid does not need to be in the top list.\n",
+ "| \e[48;2;80;80;80mp \e[0m pause/unpause the view.\n",
"|\e[44m5. Reference\e[49m \n",
"|More information about recon:proc_count/2 and recon:proc_window/3 \n",
diff --git a/src/observer_cli_lib.erl b/src/observer_cli_lib.erl
index bbdc963..bb13157 100644
--- a/src/observer_cli_lib.erl
+++ b/src/observer_cli_lib.erl
@@ -35,13 +35,11 @@ uptime() ->
[?W(?GREEN, Time, 16)].
%% @doc 0.982342 -> 98.23%, 1 -> 100.0%
--spec to_percent(float()) -> string().
to_percent(Float) when Float < 0.1 -> [$0, erlang:float_to_list(Float * 100, [{decimals, 2}]), $%];
to_percent(Float) when Float < 1 -> [erlang:float_to_list(Float * 100, [{decimals, 2}]), $%];
to_percent(undefined) -> "******";
to_percent(_) -> "100.0%".
--spec to_list(term()) -> list().
to_list(Atom) when is_atom(Atom) -> atom_to_list(Atom);
to_list(Integer) when is_integer(Integer) -> integer_to_list(Integer);
to_list(Pid) when is_pid(Pid) -> erlang:pid_to_list(Pid);
@@ -251,6 +249,10 @@ parse_cmd(ViewOpts, Module, Args) ->
hide;
"`\n" ->
scheduler_usage;
+ [$< | PidStr] ->
+ to_pid(PidStr);
+ [$> | PidStr] ->
+ to_pid(PidStr);
%% {error, estale}|{error, terminated}
{error, _Reason} ->
quit;
@@ -258,7 +260,18 @@ parse_cmd(ViewOpts, Module, Args) ->
parse_integer(Number)
end.
--spec next_redraw(reference(), pos_integer()) -> reference().
+-spec to_pid(string()) -> {go_to_pid, pid()} | quit.
+to_pid(Str) ->
+ case string:tokens(Str, ".<>\n") of
+ [X, Y, Z] ->
+ {go_to_pid, list_to_pid("<" ++ X ++ "." ++ Y ++ "." ++ Z ++ ">")};
+ [Y] ->
+ {go_to_pid, list_to_pid("<0." ++ Y ++ ".0>")};
+ _ ->
+ quit
+ end.
+
+-spec next_redraw(reference() | ?INIT_TIME_REF, pos_integer()) -> reference().
next_redraw(LastTimeRef, Interval) ->
LastTimeRef =/= ?INIT_TIME_REF andalso erlang:cancel_timer(LastTimeRef),
erlang:send_after(Interval, self(), redraw).
diff --git a/src/observer_cli_mnesia.erl b/src/observer_cli_mnesia.erl
index 97987e3..1d232fd 100644
--- a/src/observer_cli_mnesia.erl
+++ b/src/observer_cli_mnesia.erl
@@ -187,6 +187,7 @@ get_table_list(HideSys, Attr) ->
_ -> get_table_list2(Owner, HideSys, Attr)
end.
+-dialyzer([{nowarn_function, [get_table_list2/3]}]).
get_table_list2(Owner, HideSys, Attr) ->
{registered_name, RegName} = process_info(Owner, registered_name),
WordSize = erlang:system_info(wordsize),
@@ -213,6 +214,8 @@ get_table_list2(Owner, HideSys, Attr) ->
end,
lists:foldl(CollectFun, [], mnesia:system_info(tables)).
+-dialyzer([{nowarn_function, [with_storage_type/2]}]).
+
with_storage_type(Id, Tab0) ->
Storage = mnesia:table_info(Id, storage_type),
with_storage_type(Id, Storage, Tab0).
diff --git a/src/observer_cli_plugin.erl b/src/observer_cli_plugin.erl
index defd9eb..21f903b 100644
--- a/src/observer_cli_plugin.erl
+++ b/src/observer_cli_plugin.erl
@@ -139,9 +139,7 @@ manager(ChildPid, SheetCache, ViewOpts) ->
start(ViewOpts#view_opts{plug = PlugOpts#plug{plugs = NewPlugs}});
{error, _} ->
manager(ChildPid, SheetCache, ViewOpts)
- end;
- _ ->
- manager(ChildPid, SheetCache, ViewOpts)
+ end
end.
update_plugins(CurIndex, Lists, UpdateItems) ->
diff --git a/src/observer_cli_process.erl b/src/observer_cli_process.erl
index dc0c0d7..3625bc6 100644
--- a/src/observer_cli_process.erl
+++ b/src/observer_cli_process.erl
@@ -2,6 +2,12 @@
-include("observer_cli.hrl").
+-dialyzer([
+ {nowarn_function, [
+ render_worker/7, render_reduction_memory/4, get_chart_format/1, chart_format/2
+ ]}
+]).
+
-export([start/3]).
%% lists:foldl(fun(_X, Acc) -> queue:in('NaN', Acc) end, queue:new(), lists:seq(1, 5))
diff --git a/src/observer_cli_system.erl b/src/observer_cli_system.erl
index b47bc71..c80babb 100644
--- a/src/observer_cli_system.erl
+++ b/src/observer_cli_system.erl
@@ -2,6 +2,7 @@
-module(observer_cli_system).
-include("observer_cli.hrl").
+-include_lib("kernel/include/net_address.hrl").
%% API
-export([start/1]).
@@ -66,9 +67,11 @@ render_worker(Cmd, Interval, LastTimeRef) ->
SbcsToMbcsCurs,
SbcsToMbcsMaxs
),
+ DistNodesInfo = get_dist_nodes_info(),
+ DistNodeView = render_dist_node_info(DistNodesInfo),
HitView = render_cache_hit_rates(CacheHitInfo, erlang:length(CacheHitInfo)),
LastLine = observer_cli_lib:render_last_line("q(quit)"),
- ?output([?CURSOR_TOP, Menu, Sys, BlockView, HitView, LastLine]),
+ ?output([?CURSOR_TOP, Menu, Sys, BlockView, DistNodeView, HitView, LastLine]),
NextTimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval),
receive
quit -> quit;
@@ -76,6 +79,92 @@ render_worker(Cmd, Interval, LastTimeRef) ->
redraw -> render_worker(Cmd, Interval, NextTimeRef)
end.
+get_dist_nodes_info() ->
+ case ets:info(sys_dist, size) of
+ undefined ->
+ [];
+ 0 ->
+ [];
+ _ ->
+ {ok, DistNodesInfo} = net_kernel:nodes_info(),
+ DistNodesInfo
+ end.
+
+render_dist_node_info([]) ->
+ [];
+render_dist_node_info(DistNodesInfo) ->
+ Title = ?render([
+ ?UNDERLINE,
+ ?GRAY_BG,
+ ?W("Node", 30),
+ ?W("Dist Node Queue Size Bytes", 20),
+ ?W("Percent", 7),
+ ?W("Address", 19),
+ ?W("In", 11),
+ ?W("Out", 11),
+ ?W("Type", 7),
+ ?W("State", 10)
+ ]),
+ Limit = erlang:system_info(dist_buf_busy_limit),
+ LimitStr = integer_to_list(Limit),
+ View = lists:map(
+ fun({Node, Info}) ->
+ State = proplists:get_value(state, Info),
+ Type = proplists:get_value(type, Info),
+ Address = get_address(Info),
+ In = proplists:get_value(in, Info),
+ Out = proplists:get_value(out, Info),
+ QueueSize = get_dist_queue_size(Node),
+ QueueSizeStr = observer_cli_lib:to_list(QueueSize),
+ QueueSizeLimitStr = QueueSizeStr ++ "/" ++ LimitStr,
+ Percent =
+ case is_integer(QueueSize) of
+ true ->
+ Float = QueueSize / Limit,
+ [erlang:float_to_list(Float * 100, [{decimals, 2}]), $%];
+ false ->
+ "unsupported"
+ end,
+ ?render([
+ ?W(Node, 30),
+ ?W(QueueSizeLimitStr, 20),
+ ?W(Percent, 7),
+ ?W(Address, 19),
+ ?W(In, 11),
+ ?W(Out, 11),
+ ?W(Type, 7),
+ ?W(State, 10)
+ ])
+ end,
+ lists:sort(DistNodesInfo)
+ ),
+ [Title | View].
+
+get_address(Info) ->
+ #net_address{address = Address} = proplists:get_value(address, Info, #net_address{}),
+ case Address of
+ {Addr, Port} ->
+ case inet:ntoa(Addr) of
+ {error, _} ->
+ AddrStr = lists:flatten(io_lib:format("~p", [Addr])),
+ AddrStr ++ ":" ++ integer_to_list(Port);
+ AddrStr ->
+ AddrStr ++ ":" ++ integer_to_list(Port)
+ end;
+ _ ->
+ "unknown"
+ end.
+
+get_dist_queue_size(Node) ->
+ case ets:lookup(sys_dist, Node) of
+ [] ->
+ not_found;
+ [Dist] ->
+ ConnId = element(3, Dist),
+ {ok, _, _, Size} = erlang:dist_get_stat(ConnId),
+ Size
+ end.
+
render_cache_hit_rates(CacheHitInfo, Len) when Len =< 8 ->
Title = ?render([
?UNDERLINE,