Current section
Files
Jump to
Current section
Files
src/observer_cli_system.erl
%%% @author zhongwen <zhongwencool@gmail.com>
-module(observer_cli_system).
-include("observer_cli.hrl").
%% net_address record from OTP lacks typed fields; keep warning local to this module.
-compile(nowarn_untyped_record).
-include_lib("kernel/include/net_address.hrl").
%% API
-export([start/1, collect_allocator_info/0]).
-ifdef(TEST).
-export([
collect_distribution_info/0,
collect_os_process_info/1,
collect_system_info/1,
collect_sys_info/1,
fill_info/2,
to_list/1,
info_fields/0,
get_cachehit_info/2,
render_system_sections/1,
render_sys_info/1,
render_sys_info/4,
render_cache_hit_rates/2,
render_block_size_info/4,
get_alloc/5,
render_dist_node_info/1,
get_address/1,
render_worker/3,
get_dist_queue_size/1,
format_count_limit/2,
collect_runtime_info/0,
alloc_info/0,
maybe_system_info/1
]).
-endif.
-define(UTIL_ALLOCATORS, [
binary_alloc,
driver_alloc,
eheap_alloc,
ets_alloc,
fix_alloc,
ll_alloc,
sl_alloc,
std_alloc,
temp_alloc
]).
%% @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().
start(#view_opts{sys = #system{interval = Interval}} = ViewOpts) ->
Pid = spawn_link(fun() ->
?output(?CLEAR),
Cmd = io_lib:format("ps -o pcpu,pmem,rss,vsz ~s", [os:getpid()]),
render_worker(Cmd, Interval, ?INIT_TIME_REF)
end),
manager(Pid, ViewOpts).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Private
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
manager(Pid, #view_opts{sys = AllocatorOpts} = ViewOpts) ->
case observer_cli_lib:parse_cmd(ViewOpts, ?MODULE, [Pid]) of
quit ->
erlang:send(Pid, quit);
{new_interval, NewInterval} ->
erlang:send(Pid, {new_interval, NewInterval}),
NewAllocate = AllocatorOpts#system{interval = NewInterval},
manager(Pid, ViewOpts#view_opts{sys = NewAllocate});
_ ->
manager(Pid, ViewOpts)
end.
render_worker(Cmd, Interval, LastTimeRef) ->
SystemInfo = collect_system_info(Cmd),
Text = "Interval: " ++ integer_to_list(Interval) ++ "ms",
Menu = observer_cli_lib:render_top_menu(allocator, Text),
LastLine = observer_cli_lib:render_footer("q(quit)"),
?output([?CURSOR_TOP, Menu] ++ render_system_sections(SystemInfo) ++ [LastLine]),
NextTimeRef = observer_cli_lib:next_redraw(LastTimeRef, Interval),
receive
quit -> quit;
{new_interval, NewInterval} -> render_worker(Cmd, NewInterval, NextTimeRef);
redraw -> render_worker(Cmd, Interval, NextTimeRef)
end.
collect_system_info(Cmd) ->
{OsProcessInfo, SysInfo} = split_os_process_info(collect_sys_info(Cmd)),
#{
os_process_info => OsProcessInfo,
sys_info => SysInfo,
allocator_info => collect_allocator_info(),
dist_nodes_info => collect_distribution_info()
}.
split_os_process_info(SysInfo) ->
lists:partition(
fun({Key, _}) -> lists:member(Key, [ps_cpu, ps_mem, ps_rss, ps_vsz]) end,
SysInfo
).
-spec collect_allocator_info() -> map().
collect_allocator_info() ->
#{
cache_hit_info => recon_alloc:cache_hit_rates(),
average_block_curs => recon_alloc:average_block_sizes(current),
average_block_maxes => recon_alloc:average_block_sizes(max),
sbcs_to_mbcs_curs =>
observer_cli_lib:sbcs_to_mbcs(?UTIL_ALLOCATORS, recon_alloc:sbcs_to_mbcs(current)),
sbcs_to_mbcs_maxes =>
observer_cli_lib:sbcs_to_mbcs(?UTIL_ALLOCATORS, recon_alloc:sbcs_to_mbcs(max))
}.
render_system_sections(SystemInfo) ->
[
render_system_info_section(SystemInfo),
render_allocator_section(SystemInfo),
render_distribution_node_section(SystemInfo),
render_cache_hit_section(SystemInfo)
].
render_system_info_section(#{os_process_info := OsProcessInfo, sys_info := SysInfo}) ->
render_sys_info(OsProcessInfo ++ SysInfo).
render_allocator_section(#{
allocator_info := #{
average_block_curs := AverageBlockCurs,
average_block_maxes := AverageBlockMaxes,
sbcs_to_mbcs_curs := SbcsToMbcsCurs,
sbcs_to_mbcs_maxes := SbcsToMbcsMaxs
}
}) ->
render_block_size_info(
AverageBlockCurs,
AverageBlockMaxes,
SbcsToMbcsCurs,
SbcsToMbcsMaxs
).
render_distribution_node_section(#{dist_nodes_info := DistNodesInfo}) ->
render_dist_node_info(DistNodesInfo).
render_cache_hit_section(#{allocator_info := #{cache_hit_info := CacheHitInfo}}) ->
render_cache_hit_rates(CacheHitInfo, erlang:length(CacheHitInfo)).
collect_distribution_info() ->
case ets:info(sys_dist, size) of
undefined ->
[empty_distribution_info(unknown, "dist disabled")];
0 ->
[empty_distribution_info(ok, "no connected nodes")];
_ ->
Limit = erlang:system_info(dist_buf_busy_limit),
case net_kernel:nodes_info() of
{ok, []} ->
[empty_distribution_info(ok, "no connected nodes")];
{ok, DistNodesInfo} ->
[
collect_distribution_node_info(DistNodeInfo, Limit)
|| DistNodeInfo <- DistNodesInfo
]
end
end.
empty_distribution_info(Health, Detail) ->
{node(), #{
health => Health,
queue_size => undefined,
queue_limit => undefined,
address => Detail,
in => "-",
out => "-",
type => "-",
state => "-"
}}.
collect_distribution_node_info({Node, Info}, Limit) ->
{Node, #{
queue_size => get_dist_queue_size(Node),
queue_limit => Limit,
address => get_address(Info),
in => proplists:get_value(in, Info),
out => proplists:get_value(out, Info),
type => proplists:get_value(type, Info),
state => proplists:get_value(state, Info)
}}.
render_dist_node_info([]) ->
render_dist_node_info([empty_distribution_info(ok, "no connected nodes")]);
render_dist_node_info(DistNodesInfo) ->
[HealthW, NodeW, QueueW, PercentW, AddressW, InW, OutW, TypeW, StateW] = dist_node_widths(),
Title = ?render([
?UNDERLINE,
?W2(?GRAY_BG, "Health", HealthW),
?UNDERLINE,
?W2(?GRAY_BG, "Node", NodeW),
?UNDERLINE,
?W2(?GRAY_BG, "Dist Queue", QueueW),
?UNDERLINE,
?W2(?GRAY_BG, "Percent", PercentW),
?UNDERLINE,
?W2(?GRAY_BG, "Address", AddressW),
?UNDERLINE,
?W2(?GRAY_BG, "In", InW),
?UNDERLINE,
?W2(?GRAY_BG, "Out", OutW),
?UNDERLINE,
?W2(?GRAY_BG, "Type", TypeW),
?UNDERLINE,
?W2(?GRAY_BG, "State", StateW)
]),
View = lists:map(
fun({Node, Info}) ->
State = maps:get(state, Info),
Type = maps:get(type, Info),
Address = maps:get(address, Info),
In = maps:get(in, Info),
Out = maps:get(out, Info),
QueueSize = maps:get(queue_size, Info),
Limit = maps:get(queue_limit, Info),
Health = maps:get(health, Info, dist_node_health(Info)),
?render([
?W2(dist_health_color(Health), Health, HealthW),
?W2(?RESET, Node, NodeW),
?W2(?RESET, dist_queue_text(QueueSize, Limit), QueueW),
?W2(?RESET, dist_percent_text(QueueSize, Limit), PercentW),
?W2(?RESET, Address, AddressW),
?W2(?RESET, In, InW),
?W2(?RESET, Out, OutW),
?W2(?RESET, Type, TypeW),
?W2(?RESET, State, StateW)
])
end,
lists:sort(DistNodesInfo)
),
[Title | View].
dist_node_widths() ->
observer_cli_lib:weighted_widths(
[7, 28, 14, 7, 20, 10, 10, 7, 8],
[0, 4, 1, 0, 4, 0, 0, 0, 0]
).
dist_node_health(#{state := State}) when State =/= up ->
down;
dist_node_health(#{queue_size := QueueSize}) when not is_integer(QueueSize) ->
unknown;
dist_node_health(#{queue_size := QueueSize, queue_limit := Limit}) when
is_integer(QueueSize), is_integer(Limit), Limit > 0, QueueSize / Limit >= 0.85
->
warn;
dist_node_health(_Info) ->
ok.
dist_health_color(ok) -> ?GREEN;
dist_health_color(warn) -> ?YELLOW;
dist_health_color(down) -> ?RED;
dist_health_color(unknown) -> ?YELLOW.
dist_queue_text(undefined, undefined) ->
"-";
dist_queue_text(QueueSize, Limit) ->
observer_cli_lib:to_list(QueueSize) ++ "/" ++ observer_cli_lib:to_list(Limit).
dist_percent_text(undefined, undefined) ->
"-";
dist_percent_text(QueueSize, Limit) when is_integer(QueueSize), is_integer(Limit), Limit > 0 ->
Float = QueueSize / Limit,
[erlang:float_to_list(Float * 100, [{decimals, 2}]), $%];
dist_percent_text(_QueueSize, _Limit) ->
"unsupported".
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,
?GRAY_BG,
?W("Instance", 8),
?W("Hits", 10),
?W("Calls", 11),
?W("Hit Rate", 98)
]),
View = [
begin
[{hit_rate, HitRate}, {hits, Hit}, {calls, Call}] = proplists:get_value(
{instance, Seq},
CacheHitInfo
),
HitRateStr = observer_cli_lib:to_percent(HitRate),
SeqStr = lists:flatten(io_lib:format("~2..0w", [Seq])),
TrueHitRate =
case Hit == 0 andalso Call == 0 of
true -> 0;
false -> HitRate
end,
Process = lists:duplicate(trunc(TrueHitRate * 91), "|"),
?render([
?W(SeqStr, 8),
?W(Hit, 10),
?W(Call, 11),
?W(Process, 89),
?W(HitRateStr, 6)
])
end
|| Seq <- lists:seq(0, Len - 1)
],
[Title | View];
render_cache_hit_rates(CacheHitInfo, Len) ->
[HitCalls1W, HitCalls2W, HitCalls3W, HitCalls4W] = cache_hit_title_widths(),
Title = ?render([
?UNDERLINE,
?GRAY_BG,
"IN|",
?W(" Hits/Calls", HitCalls1W),
?W("HitRate", 6),
"IN|",
?W(" Hits/Calls", HitCalls2W),
?W("HitRate", 6),
"IN|",
?W(" Hits/Calls", HitCalls3W),
?W("HitRate", 6),
"IN|",
?W(" Hits/Calls", HitCalls4W),
?W("HitRate", 6)
]),
[HitCallsRow1W, HitCallsRow2W, HitCallsRow3W, HitCallsRow4W] = cache_hit_row_widths(),
Num = Len div 4,
Rows = [
begin
Seq2 = Seq1 + Num,
Seq3 = Seq2 + Num,
Seq4 = Seq3 + Num,
{SeqStr1, Hit1, Call1, HitRateStr1} = get_cachehit_info(Seq1, CacheHitInfo),
{SeqStr2, Hit2, Call2, HitRateStr2} = get_cachehit_info(Seq2, CacheHitInfo),
{SeqStr3, Hit3, Call3, HitRateStr3} = get_cachehit_info(Seq3, CacheHitInfo),
{SeqStr4, Hit4, Call4, HitRateStr4} = get_cachehit_info(Seq4, CacheHitInfo),
?render([
SeqStr1,
?W([Hit1, "/", Call1], HitCallsRow1W),
?W(HitRateStr1, 6),
SeqStr2,
?W([Hit2, "/", Call2], HitCallsRow2W),
?W(HitRateStr2, 6),
SeqStr3,
?W([Hit3, "/", Call3], HitCallsRow3W),
?W(HitRateStr3, 6),
SeqStr4,
?W([Hit4, "/", Call4], HitCallsRow4W),
?W(HitRateStr4, 6)
])
end
|| Seq1 <- lists:seq(1, Num)
],
[Title | Rows].
render_block_size_info(AverageBlockCurs, AverageBlockMaxes, SbcsToMbcsCurs, SbcsToMbcsMaxs) ->
[AllocatorW, CurrentMbcsW, MaxMbcsW, CurrentSbcsW, MaxSbcsW, CurrentSbcsToMbcsW, MaxSbcsToMbcsW] =
block_size_widths(),
Title = ?render([
?UNDERLINE,
?GRAY_BG,
?W("Allocator Type", AllocatorW),
?W("Current Mbcs", CurrentMbcsW),
?W("Max Mbcs", MaxMbcsW),
?W("Current Sbcs", CurrentSbcsW),
?W("Max Sbcs", MaxSbcsW),
?W("Current SbcsToMbcs", CurrentSbcsToMbcsW),
?W("Max SbcsToMbcs", MaxSbcsToMbcsW)
]),
View = [
begin
[Type, CMC, MMC, CSC, MSBC, CSTM, MSTM] =
get_alloc(
AllocKey,
AverageBlockCurs,
AverageBlockMaxes,
SbcsToMbcsCurs,
SbcsToMbcsMaxs
),
?render([
?W(Type, AllocatorW),
?W(CMC, CurrentMbcsW),
?W(MMC, MaxMbcsW),
?W(CSC, CurrentSbcsW),
?W(MSBC, MaxSbcsW),
?W(CSTM, CurrentSbcsToMbcsW),
?W(MSTM, MaxSbcsToMbcsW)
])
end
|| AllocKey <- ?UTIL_ALLOCATORS
],
[Title | View].
cache_hit_title_widths() ->
observer_cli_lib:weighted_widths([20, 20, 20, 19], [1, 1, 1, 1]).
cache_hit_row_widths() ->
observer_cli_lib:weighted_widths([19, 19, 19, 18], [1, 1, 1, 1]).
block_size_widths() ->
observer_cli_lib:weighted_widths(
[16, 16, 16, 16, 16, 19, 19],
[0, 1, 1, 1, 1, 2, 2]
).
get_alloc(Key, Curs, Maxes, STMCurs, STMMaxes) ->
CurRes = proplists:get_value(Key, Curs),
MaxRes = proplists:get_value(Key, Maxes),
CurMbcs = proplists:get_value(mbcs, CurRes),
CurSbcs = proplists:get_value(sbcs, CurRes),
MaxMbcs = proplists:get_value(mbcs, MaxRes),
MaxSbcs = proplists:get_value(sbcs, MaxRes),
[
atom_to_list(Key),
observer_cli_lib:to_byte(CurMbcs),
observer_cli_lib:to_byte(MaxMbcs),
observer_cli_lib:to_byte(CurSbcs),
observer_cli_lib:to_byte(MaxSbcs),
proplists:get_value(Key, STMCurs),
proplists:get_value(Key, STMMaxes)
].
render_sys_info(SysInfo) ->
{Info, Stat} = info_fields(),
SystemAndCPU = fill_info(Info, SysInfo),
MemAndStatistics = fill_info(Stat, SysInfo),
System = proplists:get_value("System and Architecture", SystemAndCPU),
CPU = proplists:get_value("CPU's and Threads", SystemAndCPU),
{_, _, Memory} = lists:keyfind("Memory Usage", 1, MemAndStatistics),
{_, _, Statistics} = lists:keyfind("Statistics", 1, MemAndStatistics),
render_sys_info(System, CPU, Memory, Statistics) ++ render_runtime_limit_info(SysInfo).
collect_sys_info(Cmd) ->
collect_os_process_info(Cmd) ++ collect_runtime_info().
render_sys_info(System, CPU, Memory, Statistics) ->
[
SystemTitleW,
SystemStateTitleW,
CpuTitleW,
CpuStateTitleW,
MemoryTitleW,
MemoryStateTitleW,
StatisticsTitleW,
StatisticsStateTitleW
] =
sys_info_title_widths(),
Title = ?render([
?GRAY_BG,
?UNDERLINE,
?W("System/Architecture", SystemTitleW),
?W("State", SystemStateTitleW),
?W("CPU's and Threads", CpuTitleW),
?W("State", CpuStateTitleW),
?W("Memory Usage", MemoryTitleW),
?W("State", MemoryStateTitleW),
?W("Statistics", StatisticsTitleW),
?W("State", StatisticsStateTitleW)
]),
[
SystemW,
SystemStateW,
CpuW,
CpuStateW,
MemoryW,
MemoryValueW,
MemoryPercentW,
StatisticsW,
StatisticsStateW
] =
sys_info_row_widths(),
NewSystem = [
begin
{Key, Value}
end
|| {Key, Value} <- System,
Key =/= "Compiled for" andalso Key =/= "smp Support"
],
[{_, TotalMem} | _R] = Memory,
{bytes, TotalMemInt} = TotalMem,
Row = [
begin
{SysKey, SysVal} = lists:nth(Pos, NewSystem),
{CpuKey, CpuVal} = lists:nth(Pos, CPU),
{MemKey, MemVal} = lists:nth(Pos, Memory),
{StatisticsKey, StatisticsVal} = lists:nth(Pos, Statistics),
{bytes, MemValInt} = MemVal,
Percent = observer_cli_lib:to_percent(MemValInt / TotalMemInt),
?render([
?W(SysKey, SystemW),
?W(to_list(SysVal), SystemStateW),
?W(CpuKey, CpuW),
?W(to_list(CpuVal), CpuStateW),
?W(MemKey, MemoryW),
?W(observer_cli_lib:to_byte(MemValInt), MemoryValueW),
?W(Percent, MemoryPercentW),
?W(StatisticsKey, StatisticsW),
?W(to_list(StatisticsVal), StatisticsStateW)
])
end
|| Pos <- lists:seq(1, 6)
],
[CompiledForW, CompiledValueW] = compiled_for_widths(),
Compile = ?render([
?UNDERLINE,
?W("compiled for", CompiledForW),
?W(to_list(proplists:get_value("Compiled for", System)), CompiledValueW)
]),
[Title | (Row ++ [Compile])].
sys_info_title_widths() ->
observer_cli_lib:weighted_widths(
[22, 8, 23, 7, 11, 22, 11, 11],
[0, 1, 0, 1, 0, 1, 0, 1]
).
sys_info_row_widths() ->
observer_cli_lib:weighted_widths(
[22, 8, 23, 7, 11, 13, 6, 11, 11],
[0, 1, 0, 1, 0, 1, 0, 0, 1]
).
compiled_for_widths() ->
observer_cli_lib:weighted_widths([22, 111], [0, 1]).
render_runtime_limit_info(SysInfo) ->
Widths = [Metric1W, Value1W, Metric2W, Value2W, Metric3W, Value3W] = runtime_limit_widths(),
Title = ?render([
?UNDERLINE,
?GRAY_BG,
?W("System Statistics / Limit", Metric1W),
?W("Value", Value1W),
?W("System Statistics / Limit", Metric2W),
?W("Value", Value2W),
?W("System Statistics / Limit", Metric3W),
?W("Value", Value3W)
]),
Rows = [
[
{"Processes", format_count_limit(process_count, process_limit, SysInfo)},
{"Dirty CPU schedulers", to_list(proplists:get_value(dirty_cpu_schedulers, SysInfo))},
{"Distribution buffer busy limit",
to_list(proplists:get_value(dist_buf_busy_limit, SysInfo))}
],
[
{"Ports", format_count_limit(port_count, port_limit, SysInfo)},
{"Online dirty CPU schedulers",
to_list(proplists:get_value(dirty_cpu_schedulers_online, SysInfo))},
{"Modules", to_list(proplists:get_value(module_count, SysInfo))}
],
[
{"Atoms", format_count_limit(atom_count, atom_limit, SysInfo)},
{"Run Queue", to_list(proplists:get_value(run_queue, SysInfo))},
{"ETS", format_count_limit(ets_count, ets_limit, SysInfo)}
]
],
[Title | [render_runtime_limit_row(Row, Widths) || Row <- Rows]].
render_runtime_limit_row(
[{Name1, Value1}, {Name2, Value2}, {Name3, Value3}],
[Metric1W, Value1W, Metric2W, Value2W, Metric3W, Value3W]
) ->
?render([
?W(Name1, Metric1W),
?W(Value1, Value1W),
?W(Name2, Metric2W),
?W(Value2, Value2W),
?W(Name3, Metric3W),
?W(Value3, Value3W)
]).
runtime_limit_widths() ->
observer_cli_lib:weighted_widths([30, 10, 30, 10, 30, 11], [0, 1, 0, 1, 0, 1]).
format_count_limit(CountKey, LimitKey, SysInfo) ->
Count = proplists:get_value(CountKey, SysInfo),
Limit = proplists:get_value(LimitKey, SysInfo),
format_count_limit(Count, Limit).
format_count_limit(Count, Limit) when is_integer(Count), is_integer(Limit), Limit > 0 ->
[
integer_to_list(Count),
" / ",
integer_to_list(Limit),
" (",
observer_cli_lib:to_percent(Count / Limit),
" used)"
];
format_count_limit(Count, Limit) ->
[to_list(Count), " / ", to_list(Limit)].
collect_os_process_info(Cmd) ->
[_, CmdValue | _] = string:split(os:cmd(Cmd), "\n", all),
[CpuPsV, MemPsV, RssPsV, VszPsV] =
case lists:filter(fun(Y) -> Y =/= [] end, string:split(CmdValue, " ", all)) of
[] -> ["--", "--", "--", "--"];
[V1, V2, V3, V4] -> [V1, V2, list_to_integer(V3) * 1024, list_to_integer(V4) * 1024]
end,
[
{ps_cpu, CpuPsV ++ "%"},
{ps_mem, MemPsV ++ "%"},
{ps_rss, RssPsV},
{ps_vsz, VszPsV}
].
collect_runtime_info() ->
MemInfo =
try erlang:memory() of
Mem -> Mem
catch
_:_ -> []
end,
SchedulersOnline = erlang:system_info(schedulers_online),
SchedulersAvailable =
case erlang:system_info(multi_scheduling) of
enabled -> SchedulersOnline;
_ -> 1
end,
{{_, Input}, {_, Output}} = erlang:statistics(io),
[
{run_queue, erlang:statistics(run_queue)},
{io_input, Input},
{io_output, Output},
{logical_processors, erlang:system_info(logical_processors)},
{logical_processors_online, erlang:system_info(logical_processors_online)},
{logical_processors_available, erlang:system_info(logical_processors_available)},
{schedulers, erlang:system_info(schedulers)},
{schedulers_online, SchedulersOnline},
{schedulers_available, SchedulersAvailable},
{dirty_cpu_schedulers, maybe_system_info(dirty_cpu_schedulers)},
{dirty_cpu_schedulers_online, maybe_system_info(dirty_cpu_schedulers_online)},
{otp_release, erlang:system_info(otp_release)},
{version, erlang:system_info(version)},
{system_architecture, erlang:system_info(system_architecture)},
{kernel_poll, erlang:system_info(kernel_poll)},
{smp_support, erlang:system_info(smp_support)},
{threads, erlang:system_info(threads)},
{thread_pool_size, erlang:system_info(thread_pool_size)},
{wordsize_internal, erlang:system_info({wordsize, internal})},
{wordsize_external, erlang:system_info({wordsize, external})},
{alloc_info, alloc_info()},
{module_count, erlang:length(code:all_loaded())},
{process_count, erlang:system_info(process_count)},
{process_limit, erlang:system_info(process_limit)},
{port_count, erlang:system_info(port_count)},
{port_limit, erlang:system_info(port_limit)},
{atom_count, erlang:system_info(atom_count)},
{atom_limit, maybe_system_info(atom_limit)},
{ets_count, maybe_system_info(ets_count)},
{ets_limit, maybe_system_info(ets_limit)},
{dist_buf_busy_limit, maybe_system_info(dist_buf_busy_limit)}
| MemInfo
].
alloc_info() ->
Alloc = erlang:system_info(alloc_util_allocators),
try erlang:system_info({allocator_sizes, Alloc}) of
Allocators -> Allocators
catch
_:_ -> []
end.
maybe_system_info(Key) ->
try erlang:system_info(Key) of
Value -> Value
catch
_:badarg -> undefined
end.
fill_info([{dynamic, Key} | Rest], Data) when is_atom(Key) ->
case proplists:get_value(Key, Data) of
undefined -> [undefined | fill_info(Rest, Data)];
{Str, Value} -> [{Str, Value} | fill_info(Rest, Data)]
end;
fill_info([{Str, Key} | Rest], Data) when is_atom(Key) ->
case proplists:get_value(Key, Data) of
undefined -> [undefined | fill_info(Rest, Data)];
Value -> [{Str, Value} | fill_info(Rest, Data)]
end;
fill_info([{Str, Attrib, Key} | Rest], Data) when is_atom(Key) ->
case proplists:get_value(Key, Data) of
undefined -> [undefined | fill_info(Rest, Data)];
Value -> [{Str, Attrib, Value} | fill_info(Rest, Data)]
end;
fill_info([{Str, {Format, Key}} | Rest], Data) when is_atom(Key) ->
case proplists:get_value(Key, Data) of
undefined -> [undefined | fill_info(Rest, Data)];
Value -> [{Str, {Format, Value}} | fill_info(Rest, Data)]
end;
fill_info([{Str, Attrib, {Format, Key}} | Rest], Data) when is_atom(Key) ->
case proplists:get_value(Key, Data) of
undefined -> [undefined | fill_info(Rest, Data)];
Value -> [{Str, Attrib, {Format, Value}} | fill_info(Rest, Data)]
end;
fill_info([{Str, SubStructure} | Rest], Data) when is_list(SubStructure) ->
[{Str, fill_info(SubStructure, Data)} | fill_info(Rest, Data)];
fill_info([{Str, Attrib, SubStructure} | Rest], Data) ->
[{Str, Attrib, fill_info(SubStructure, Data)} | fill_info(Rest, Data)];
fill_info([], _) ->
[].
to_list(Val) when is_integer(Val) -> integer_to_list(Val);
to_list(Val) when is_atom(Val) -> atom_to_list(Val);
to_list({bytes, Val}) -> observer_cli_lib:to_byte(Val);
to_list(Val) -> Val.
info_fields() ->
Info = [
{"System and Architecture", [
{"System Version", otp_release},
{"Erts Version", version},
{"Compiled for", system_architecture},
{"Emulator Wordsize", wordsize_external},
{"Process Wordsize", wordsize_internal},
{"Smp Support", smp_support},
{"Thread Support", threads},
{"Async thread pool size", thread_pool_size}
]},
{"CPU's and Threads", [
{"Logical CPU's", logical_processors},
{"Online Logical CPU's", logical_processors_online},
{"Available Logical CPU's", logical_processors_available},
{"Schedulers", schedulers},
{"Online schedulers", schedulers_online},
{"Available schedulers", schedulers_available}
]}
],
Stat = [
{"Memory Usage", right, [
{"Total", {bytes, total}},
{"Processes", {bytes, processes}},
{"Atoms", {bytes, atom}},
{"Binaries", {bytes, binary}},
{"Code", {bytes, code}},
{"Ets", {bytes, ets}}
]},
{"Statistics", right, [
{"ps -o pcpu", ps_cpu},
{"ps -o pmem", ps_mem},
{"ps -o rss", {bytes, ps_rss}},
{"ps -o vsz", {bytes, ps_vsz}},
{"Total IOIn", {bytes, io_input}},
{"Total IOOut", {bytes, io_output}}
]}
],
{Info, Stat}.
get_cachehit_info(Seq, CacheHitInfo) ->
[{hit_rate, HitRate}, {hits, Hit}, {calls, Call}] = proplists:get_value(
{instance, Seq},
CacheHitInfo
),
SeqStr = lists:flatten(io_lib:format("\e[92m~2..0w| \e[0m", [Seq])),
HitRateStr = observer_cli_lib:to_percent(HitRate),
{SeqStr, integer_to_list(Hit), integer_to_list(Call), HitRateStr}.