Skip to content

Commit 2522bba

Browse files
SteffenDEjosevalim
authored andcommitted
IEx.Helpers.process_info/1 (#14418)
1 parent d00f172 commit 2522bba

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed

lib/iex/lib/iex/helpers.ex

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ defmodule IEx.Helpers do
4040
* `pid/3` - creates a PID with the 3 integer arguments passed
4141
* `port/1` - creates a port from a string
4242
* `port/2` - creates a port with the 2 non-negative integers passed
43+
* `process_info/1` - returns information about the given process
4344
* `pwd/0` - prints the current working directory
4445
* `r/1` - recompiles the given module's source file
4546
* `recompile/0` - recompiles the current project
@@ -817,6 +818,168 @@ defmodule IEx.Helpers do
817818

818819
defp pad_key(key), do: String.pad_trailing(key, 21, " ")
819820

821+
@process_info_keys_and_labels [
822+
{:initial_call, "Initial call"},
823+
{:dictionary, "Dictionary"},
824+
{:registered_name, "Registered name"},
825+
{:current_function, "Current function"},
826+
{:status, "Status"},
827+
{:message_queue_len, "Message queue length"},
828+
{:trap_exit, "Trap exit"},
829+
{:priority, "Priority"},
830+
{:group_leader, "Group leader"},
831+
{:reductions, "Reductions"},
832+
{:links, "Links"},
833+
{:monitors, "Monitors"},
834+
{:memory, "Memory"},
835+
{:total_heap_size, "Total heap size"},
836+
{:heap_size, "Heap size"},
837+
{:stack_size, "Stack size"},
838+
{:current_stacktrace, "Current stacktrace"}
839+
]
840+
@process_info_keys Enum.map(@process_info_keys_and_labels, fn {key, _} -> key end)
841+
@process_info_label_mapping Map.new(@process_info_keys_and_labels)
842+
843+
@doc """
844+
Prints information about the given process.
845+
846+
Includes a generic overview and details such as the linked and monitored processes,
847+
the memory usage and the current stacktrace.
848+
849+
## Examples
850+
851+
iex> process_info(self())
852+
...
853+
iex> process_info({:via, Registry, {MyApp.Registry, :name}})
854+
...
855+
856+
"""
857+
@doc since: "1.19.0"
858+
def process_info(pid) do
859+
with pid when is_pid(pid) <- GenServer.whereis(pid),
860+
info when is_list(info) <-
861+
:erpc.call(node(pid), :erlang, :process_info, [pid, @process_info_keys]) do
862+
info = Map.new(info)
863+
864+
IO.puts(IEx.color(:eval_result, ["\n# Process ", inspect(pid)]))
865+
866+
print_process_overview(info)
867+
print_process_links(info[:links])
868+
print_process_monitors(info[:monitors])
869+
print_process_memory(info)
870+
print_process_stacktrace(info[:current_stacktrace])
871+
else
872+
_ ->
873+
IO.puts(
874+
IEx.color(
875+
:eval_error,
876+
"Failed to get process info. Either the process was not found or is not alive."
877+
)
878+
)
879+
end
880+
881+
dont_display_result()
882+
end
883+
884+
defp print_process_overview(info) do
885+
print_pane("Overview")
886+
887+
for key <- [
888+
:initial_call,
889+
:current_function,
890+
:registered_name,
891+
:status,
892+
:message_queue_len,
893+
:group_leader,
894+
:priority,
895+
:trap_exit,
896+
:reductions
897+
] do
898+
print_entry(
899+
@process_info_label_mapping[key],
900+
inspect(info[key], printable_limit: 256, limit: 5)
901+
)
902+
end
903+
end
904+
905+
defp print_process_links([]), do: :ok
906+
907+
defp print_process_links(ports_and_pids) do
908+
print_pane("Links")
909+
910+
for link <- ports_and_pids do
911+
print_entry(inspect(link), pid_or_port_details(link))
912+
end
913+
end
914+
915+
defp print_process_monitors([]), do: :ok
916+
917+
defp print_process_monitors(monitors) do
918+
print_pane("Monitors")
919+
920+
for {_, pid_or_port} <- monitors do
921+
print_entry(inspect(pid_or_port), pid_or_port_details(pid_or_port))
922+
end
923+
end
924+
925+
defp print_process_memory(info) do
926+
print_pane("Memory")
927+
928+
for key <- [:memory, :total_heap_size, :heap_size, :stack_size] do
929+
print_entry(@process_info_label_mapping[key], format_bytes(info[key]))
930+
end
931+
end
932+
933+
defp print_process_stacktrace([]), do: :ok
934+
935+
defp print_process_stacktrace(stacktrace) do
936+
print_pane("Current stacktrace")
937+
938+
IO.puts(IEx.color(:eval_info, Exception.format_stacktrace(stacktrace)))
939+
end
940+
941+
defp pid_or_port_details(pid) when is_pid(pid), do: to_process_details(pid)
942+
defp pid_or_port_details(name) when is_atom(name), do: to_process_details(name)
943+
defp pid_or_port_details(port) when is_port(port), do: to_port_details(port)
944+
defp pid_or_port_details(reference) when is_reference(reference), do: reference
945+
946+
defp to_process_details(pid) when is_pid(pid) do
947+
case Process.info(pid, [:initial_call, :dictionary, :registered_name]) do
948+
[{:initial_call, initial_call}, {:dictionary, dictionary}, {:registered_name, name}] ->
949+
initial_call = Keyword.get(dictionary, :"$initial_call", initial_call)
950+
951+
format_registered_name(name) ||
952+
format_process_label(Keyword.get(dictionary, :"$process_label")) ||
953+
format_initial_call(initial_call)
954+
955+
_ ->
956+
"-"
957+
end
958+
end
959+
960+
defp to_process_details(name) when is_atom(name) do
961+
Process.whereis(name)
962+
|> to_process_details()
963+
end
964+
965+
defp format_process_label(nil), do: nil
966+
defp format_process_label(label) when is_binary(label), do: label
967+
defp format_process_label(label), do: inspect(label)
968+
969+
defp format_registered_name([]), do: nil
970+
defp format_registered_name(name), do: inspect(name)
971+
972+
defp format_initial_call({:supervisor, mod, arity}), do: Exception.format_mfa(mod, :init, arity)
973+
defp format_initial_call({m, f, a}), do: Exception.format_mfa(m, f, a)
974+
defp format_initial_call(nil), do: nil
975+
976+
defp to_port_details(port) when is_port(port) do
977+
case Port.info(port, :name) do
978+
{:name, name} -> name
979+
_ -> "-"
980+
end
981+
end
982+
820983
@doc """
821984
Clears out all messages sent to the shell's inbox and prints them out.
822985
"""

lib/iex/test/iex/helpers_test.exs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,37 @@ defmodule IEx.HelpersTest do
16251625
end
16261626
end
16271627

1628+
describe "process_info/1" do
1629+
test "returns information about a process" do
1630+
result = capture_io(fn -> process_info(self()) end)
1631+
1632+
assert result =~ "Process #{inspect(self())}"
1633+
assert result =~ "## Overview"
1634+
assert result =~ "Initial call"
1635+
assert result =~ "## Memory"
1636+
assert result =~ "## Current stacktrace"
1637+
assert result =~ "IEx.Helpers.process_info/1"
1638+
1639+
refute result =~ "## Links"
1640+
refute result =~ "## Monitors"
1641+
end
1642+
1643+
test "includes process links and monitors" do
1644+
pid =
1645+
spawn_link(fn ->
1646+
Process.register(self(), :iex_process_info_link_test)
1647+
Process.sleep(:infinity)
1648+
end)
1649+
1650+
Process.monitor(pid)
1651+
1652+
result = capture_io(fn -> process_info(self()) end)
1653+
assert result =~ "## Links"
1654+
assert result =~ "## Monitors"
1655+
assert result =~ ":iex_process_info_link_test"
1656+
end
1657+
end
1658+
16281659
defp test_module_code do
16291660
"""
16301661
defmodule Sample do

0 commit comments

Comments
 (0)