Skip to content

Commit ec4cc25

Browse files
authored
IEx.Helpers.process_info/1 (#14418)
1 parent 7ca67e9 commit ec4cc25

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
@@ -44,6 +44,7 @@ defmodule IEx.Helpers do
4444
* `pid/3` - creates a PID with the 3 integer arguments passed
4545
* `port/1` - creates a port from a string
4646
* `port/2` - creates a port with the 2 non-negative integers passed
47+
* `process_info/1` - returns information about the given process
4748
* `pwd/0` - prints the current working directory
4849
* `r/1` - recompiles the given module's source file
4950
* `recompile/0` - recompiles the current project
@@ -827,6 +828,168 @@ defmodule IEx.Helpers do
827828

828829
defp pad_key(key), do: String.pad_trailing(key, 21, " ")
829830

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

lib/iex/test/iex/helpers_test.exs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,37 @@ defmodule IEx.HelpersTest do
16411641
end
16421642
end
16431643

1644+
describe "process_info/1" do
1645+
test "returns information about a process" do
1646+
result = capture_io(fn -> process_info(self()) end)
1647+
1648+
assert result =~ "Process #{inspect(self())}"
1649+
assert result =~ "## Overview"
1650+
assert result =~ "Initial call"
1651+
assert result =~ "## Memory"
1652+
assert result =~ "## Current stacktrace"
1653+
assert result =~ "IEx.Helpers.process_info/1"
1654+
1655+
refute result =~ "## Links"
1656+
refute result =~ "## Monitors"
1657+
end
1658+
1659+
test "includes process links and monitors" do
1660+
pid =
1661+
spawn_link(fn ->
1662+
Process.register(self(), :iex_process_info_link_test)
1663+
Process.sleep(:infinity)
1664+
end)
1665+
1666+
Process.monitor(pid)
1667+
1668+
result = capture_io(fn -> process_info(self()) end)
1669+
assert result =~ "## Links"
1670+
assert result =~ "## Monitors"
1671+
assert result =~ ":iex_process_info_link_test"
1672+
end
1673+
end
1674+
16441675
defp test_module_code do
16451676
"""
16461677
defmodule Sample do

0 commit comments

Comments
 (0)