Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/erlang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ jobs:
runs-on: ubuntu-24.04
name: Erlang/OTP ${{matrix.otp}} / rebar3 ${{matrix.rebar3}}
strategy:
fail-fast: false
matrix:
otp: ['27', '26', '25', '24']
rebar3: ['3.22']
include:
- otp: '28'
rebar3: '3.24'
- otp: '27'
rebar3: '3.22'
- otp: '26'
rebar3: '3.22'
- otp: '25'
rebar3: '3.22'
- otp: '24'
rebar3: '3.22'
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
Expand Down
5 changes: 4 additions & 1 deletion docs/changlog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

- 1.8.4
- Fix OTP 28 dialzyer warning.
- Use erlang:processes_iterator/0, erlang:process_next/1 to fold processes when OTP 28.

- 1.8.3
- Fix mnesia crash by handling unknown storage types.
- Fix OTP 27 warning.
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule ObserverCli.MixProject do
def project do
[
app: :observer_cli,
version: "1.8.3",
version: "1.8.4",
language: :erlang,
description: "observer in shell",
deps: [
Expand Down
2 changes: 1 addition & 1 deletion src/observer_cli.app.src
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{application, observer_cli, [
{description, "Visualize Erlang Nodes On The Command Line"},
{vsn, "1.8.3"},
{vsn, "1.8.4"},
{modules, [
observer_cli
]},
Expand Down
8 changes: 4 additions & 4 deletions src/observer_cli.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"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()}.
-spec start() -> no_return() | {badrpc, term()}.
start() -> start(#view_opts{}).

-spec start(Node) -> no_return | {badrpc, term()} when
-spec start(Node) -> no_return() | {badrpc, term()} when
Node :: atom() | non_neg_integer() | #view_opts{}.
start(Node) when Node =:= node() ->
start(#view_opts{});
Expand All @@ -54,7 +54,7 @@ start(Interval) when is_integer(Interval), Interval >= ?MIN_INTERVAL ->
port = Interval
}).

-spec start(Node, Cookies | Options) -> no_return | {badrpc, term()} when
-spec start(Node, Cookies | Options) -> no_return() | {badrpc, term()} when
Node :: atom(),
Cookies :: atom(),
Options :: proplists:proplist().
Expand All @@ -70,7 +70,7 @@ start(Node, Options) when is_atom(Node) andalso is_list(Options) ->
Interval = proplists:get_value(interval, Options, ?DEFAULT_INTERVAL),
rpc_start(Node, Interval).

-spec start_plugin() -> no_return.
-spec start_plugin() -> no_return().
start_plugin() ->
erlang:process_flag(trap_exit, true),
application:ensure_all_started(observer_cli),
Expand Down
86 changes: 82 additions & 4 deletions src/observer_cli_application.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
"refresh: ~wms q(quit) Positive Number(set refresh interval time ms) F/B(forward/back) Current pages is ~w"
).

%% erlang:processes_iterator/0 is not exported before OTP 27
-dialyzer([{nowarn_function, [app_info/0, app_info_iter/4]}]).
-ignore_xref({erlang, processes_iterator, 0}).
-ignore_xref({erlang, processes_next, 1}).

%% @doc List application info

-spec start(ViewOpts) -> quit when ViewOpts :: view_opts().
Expand Down Expand Up @@ -141,7 +146,73 @@ app_info() ->
Info = application:info(),
AllApps = app_status(Info),
Leaders = leader_info(Info),
app_info(AllApps, Leaders, erlang:processes(), self()).
case erlang:function_exported(erlang, processes_iterator, 0) of
true ->
app_info_iter(AllApps, Leaders, erlang:processes_iterator(), self());
false ->
app_info(AllApps, Leaders, erlang:processes(), self())
end.

app_info_iter(AllApps, Leaders, Iter, Self) ->
case erlang:processes_next(Iter) of
{Pid, NewIter} when is_pid(Pid) ->
case erlang:process_info(Pid, [group_leader, memory, reductions, message_queue_len]) of
undefined ->
app_info_iter(AllApps, Leaders, NewIter, Self);
Prop ->
[
{group_leader, Group},
{memory, Memory},
{reductions, Reds},
{message_queue_len, MsgQ}
] = Prop,
NewAllApps =
case maps:find(Group, Leaders) of
error ->
case find_group_leader(Group) of
no_group ->
{ok, {C1, M1, R1, Q1, S1, V1}} = maps:find(
no_group, AllApps
),
NewInfo = {
C1 + 1, M1 + Memory, R1 + Reds, Q1 + MsgQ, S1, V1
},
maps:put(no_group, NewInfo, AllApps);
GroupLeader ->
case maps:find(GroupLeader, Leaders) of
error ->
{ok, {C1, M1, R1, Q1, S1, V1}} = maps:find(
no_group, AllApps
),
NewInfo = {
C1 + 1,
M1 + Memory,
R1 + Reds,
Q1 + MsgQ,
S1,
V1
},
maps:put(no_group, NewInfo, AllApps);
{ok, App} ->
{ok, {C, M, R, Q, S, V}} = maps:find(App, AllApps),
maps:put(
App,
{C + 1, M + Memory, R + Reds, Q + MsgQ, S, V},
AllApps
)
end
end;
{ok, App} ->
{ok, {C, M, R, Q, S, V}} = maps:find(App, AllApps),
maps:put(
App, {C + 1, M + Memory, R + Reds, Q + MsgQ, S, V}, AllApps
)
end,
app_info_iter(NewAllApps, Leaders, NewIter, Self)
end;
none ->
AllApps
end.

app_info(AllApps, _Leaders, [], _Self) ->
AllApps;
Expand All @@ -161,9 +232,9 @@ app_info(AllApps, Leaders, [Pid | Process], Self) ->
NewAllApps =
case maps:find(Group, Leaders) of
error ->
{ok, {C1, M1, R1, Q1, S1, V1}} = maps:find(unknown, AllApps),
{ok, {C1, M1, R1, Q1, S1, V1}} = maps:find(no_group, AllApps),
NewInfo = {C1 + 1, M1 + Memory, R1 + Reds, Q1 + MsgQ, S1, V1},
maps:put(unknown, NewInfo, AllApps);
maps:put(no_group, NewInfo, AllApps);
{ok, App} ->
{ok, {C, M, R, Q, S, V}} = maps:find(App, AllApps),
maps:put(App, {C + 1, M + Memory, R + Reds, Q + MsgQ, S, V}, AllApps)
Expand Down Expand Up @@ -195,7 +266,7 @@ app_status(Info) ->
{started, Started} = lists:keyfind(started, 1, Info),
{start_p_false, StartPFalse} = lists:keyfind(start_p_false, 1, Info),
{starting, Starting} = lists:keyfind(starting, 1, Info),
R0 = #{unknown => {0, 0, 0, 0, "Unknown", "unknown"}},
R0 = #{no_group => {0, 0, 0, 0, "Unknown", "unknown"}},
R1 = lists:foldl(
fun({App, _From}, Acc) ->
Acc#{App => {0, 0, 0, 0, "Loading", "unknown"}}
Expand Down Expand Up @@ -240,3 +311,10 @@ get_version(App, Maps) ->
{ok, {_, _, _, _, _, V}} -> V;
_ -> "unknown"
end.

find_group_leader(Pid) ->
case erlang:process_info(Pid, group_leader) of
undefined -> no_group;
{group_leader, Pid} -> Pid;
{group_leader, Group} -> find_group_leader(Group)
end.
4 changes: 1 addition & 3 deletions src/observer_cli_escriptize.erl
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ run(TargetNode, Cookie, Interval) ->
case Start() of
{badrpc, _} ->
remote_load(TargetNodeAtom),
io:format("~p~n", [Start()]);
_ ->
ok
io:format("~p~n", [Start()])
end.

remote_load(Node) ->
Expand Down
2 changes: 1 addition & 1 deletion src/observer_cli_help.erl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

-define(HELP_COLUMN_WIDTH, 85).

-spec start(view_opts()) -> no_return.
-spec start(view_opts()) -> no_return().
start(#view_opts{help = #help{interval = Interval}} = ViewOpts) ->
ChildPid = spawn_link(fun() ->
Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
Expand Down
2 changes: 1 addition & 1 deletion src/observer_cli_inet.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
" sc(send_cnt) so(send_oct) cnt oct 9(port 9 info) pd/pu(page:down/up)"
).

-spec start(view_opts()) -> no_return.
-spec start(view_opts()) -> no_return().
start(#view_opts{inet = InetOpt, auto_row = AutoRow} = ViewOpts) ->
StorePid = observer_cli_store:start(),
RenderPid = spawn_link(
Expand Down
2 changes: 1 addition & 1 deletion src/observer_cli_port.erl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

-export([start/2]).

-spec start(pid(), view_opts()) -> no_return.
-spec start(pid(), view_opts()) -> no_return().
start(Port, Opts) ->
#view_opts{port = RefreshMs} = Opts,
RenderPid = spawn_link(fun() ->
Expand Down
2 changes: 1 addition & 1 deletion src/observer_cli_process.erl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
%% 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.
-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() ->
Expand Down
2 changes: 1 addition & 1 deletion src/observer_cli_system.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

%% @doc List System and Architecture, CPU's and Threads metrics in observer's system
%% List Memory Allocators: std, ll, eheap, ets, fix, binary, driver.
-spec start(ViewOpts) -> no_return when ViewOpts :: view_opts().
-spec start(ViewOpts) -> no_return() when ViewOpts :: view_opts().
start(#view_opts{sys = #system{interval = Interval}} = ViewOpts) ->
Pid = spawn_link(fun() ->
?output(?CLEAR),
Expand Down