Current section

Files

Jump to
observer_web lib observer_web apps process.ex
Raw

lib/observer_web/apps/process.ex

defmodule ObserverWeb.Apps.Process do
@moduledoc """
Retrieve process links and information
## References:
* https://github.com/shinyscorpion/wobserver
"""
alias ObserverWeb.Apps.Helper
alias ObserverWeb.Rpc
@process_full [
:registered_name,
:priority,
:trap_exit,
:initial_call,
:current_function,
:message_queue_len,
:error_handler,
:group_leader,
:links,
:memory,
:total_heap_size,
:heap_size,
:stack_size,
:min_heap_size,
:garbage_collection,
:status,
:dictionary,
:monitored_by,
:monitors
]
@doc """
Creates a complete overview of process stats based on the given `pid`.
"""
@spec info(pid :: pid()) :: :undefined | map
def info(pid) do
process_info(pid, @process_full, &structure_full/2)
end
@spec state(pid :: pid()) :: {:ok, any()} | {:error, String.t()}
def state(pid) do
if pid != self() do
try do
state = :sys.get_state(pid, 100)
{:ok, state}
catch
_, reason ->
{:error,
"Could not retrieve the state for pid: #{inspect(pid)} reason: #{inspect(reason)}"}
end
else
{:error,
"The requester’s PID is identical to the target PID, so the state cannot be requested."}
end
end
### ==========================================================================
### Private functions
### ==========================================================================
defp process_info(pid, information, structurer) do
case Rpc.pinfo(pid, information) do
:undefined -> :undefined
data -> structurer.(data, pid)
end
end
defp process_status_module(pid) do
{:status, ^pid, {:module, class}, _} = :sys.get_status(pid, 100)
class
catch
# coveralls-ignore-start
_, _ ->
:unknown
# coveralls-ignore-stop
end
defp initial_call(data) do
dictionary_init =
data
|> Keyword.get(:dictionary, [])
|> Keyword.get(:"$initial_call", nil)
case dictionary_init do
nil ->
Keyword.get(data, :initial_call, nil)
call ->
call
end
end
# Structurers
defp structure_full(data, pid) do
gc = Keyword.get(data, :garbage_collection, [])
dictionary = Keyword.get(data, :dictionary)
{state, phx_lv_socket} =
case state(pid) do
{:ok, %{socket: %Phoenix.LiveView.Socket{}} = state} ->
new_state = %{state | socket: "Phoenix.LiveView.Socket", components: "hidden"}
{to_string(:io_lib.format("~tp", [new_state])), state.socket}
{:ok, state} ->
{to_string(:io_lib.format("~tp", [state])), nil}
{:error, reason} ->
{reason, nil}
end
%{
pid: pid,
registered_name: Keyword.get(data, :registered_name, nil),
priority: Keyword.get(data, :priority, :normal),
trap_exit: Keyword.get(data, :trap_exit, false),
message_queue_len: Keyword.get(data, :message_queue_len, 0),
error_handler: Keyword.get(data, :error_handler, :none),
relations: %{
group_leader: Keyword.get(data, :group_leader, nil),
ancestors: Keyword.get(dictionary, :"$ancestors", []),
links: Keyword.get(data, :links, nil),
monitored_by: Keyword.get(data, :monitored_by, nil),
monitors: Keyword.get(data, :monitors, nil)
},
memory: %{
total: Keyword.get(data, :memory, 0),
stack_and_heap: Keyword.get(data, :total_heap_size, 0),
heap_size: Keyword.get(data, :heap_size, 0),
stack_size: Keyword.get(data, :stack_size, 0),
gc_min_heap_size: Keyword.get(gc, :min_heap_size, 0),
gc_full_sweep_after: Keyword.get(gc, :fullsweep_after, 0)
},
meta: structure_meta(data, pid),
state: state,
phx_lv_socket: phx_lv_socket
}
end
defp structure_meta(data, pid) do
init = initial_call(data)
class =
case init do
{:supervisor, _, _} -> :supervisor
{:application_master, _, _} -> :application
_ -> process_status_module(pid)
end
%{
init: Helper.format_function(init),
current: Helper.format_function(Keyword.get(data, :current_function)),
status: Keyword.get(data, :status),
class: class
}
end
end