Current section
Files
Jump to
Current section
Files
lib/wobserver/util/process.ex
defmodule Wobserver.Util.Process do
@moduledoc ~S"""
Process and pid handling.
"""
import Wobserver.Util.Helper, only: [string_to_module: 1, format_function: 1]
@process_summary [
:registered_name,
:initial_call,
:memory,
:reductions,
:current_function,
:message_queue_len,
:dictionary,
]
@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,
]
@process_meta [
:initial_call,
:current_function,
:status,
:dictionary,
]
@doc ~S"""
Turns the argument `pid` into a pid or if not possible returns `nil`.
It will accept:
- pids
- atoms / module names (registered processes)
- single integers
- a list of 3 integers
- a tuple of 3 integers
- a charlist in the format: `'<0.0.0>'`
- a String in the following formats:
- `"#PID<0.0.0>"`
- `"<0.0.0>"`
- atom / module name
Example:
```bash
iex> Wobserver.Util.Process.pid pid(0, 33, 0)
#PID<0.33.0>
```
```bash
iex> Wobserver.Util.Process.pid :cowboy_sup
#PID<0.253.0>
```
```bash
iex> Wobserver.Util.Process.pid Logger
#PID<0.213.0>
```
```bash
iex> Wobserver.Util.Process.pid 33
#PID<0.33.0>
```
```bash
iex> Wobserver.Util.Process.pid [0, 33, 0]
#PID<0.33.0>
```
```bash
iex> Wobserver.Util.Process.pid '<0.33.0>'
#PID<0.33.0>
```
```bash
iex> Wobserver.Util.Process.pid {0, 33, 0}
#PID<0.33.0>
```
```bash
iex> Wobserver.Util.Process.pid "#PID<0.33.0>"
#PID<0.33.0>
```
```bash
iex> Wobserver.Util.Process.pid "<0.33.0>"
#PID<0.33.0>
```
```bash
iex> Wobserver.Util.Process.pid "cowboy_sup"
#PID<0.253.0>
```
```bash
iex> Wobserver.Util.Process.pid "Logger"
#PID<0.213.0>
```
```bash
iex> Wobserver.Util.Process.pid 4.5
nil
```
"""
@spec pid(
pid :: pid | atom | list | binary | integer | {integer, integer, integer}
) :: pid | nil
def pid(pid)
def pid(pid) when is_pid(pid), do: pid
def pid(pid) when is_atom(pid), do: Process.whereis pid
def pid(pid) when is_integer(pid), do: pid "<0.#{pid}.0>"
def pid([a, b, c]), do: pid "<#{a}.#{b}.#{c}>"
def pid(pid) when is_list(pid), do: :erlang.list_to_pid(pid)
def pid({a, b, c}), do: pid "<#{a}.#{b}.#{c}>"
def pid("#PID" <> pid), do: pid |> String.to_charlist |> pid()
def pid(pid = ("<" <> _)), do: pid |> String.to_charlist |> pid()
def pid(pid) when is_binary(pid), do: pid |> string_to_module() |> pid()
def pid(_), do: nil
@doc ~S"""
Turns the argument `pid` into a pid or if not possible raises error.
For example see: `Wobserver.Util.Process.pid/1`.
"""
@spec pid!(
pid :: pid | list | binary | integer | {integer, integer, integer}
) :: pid
def pid!(pid) do
case pid(pid) do
nil ->
raise ArgumentError, message: "Can not convert #{inspect pid} to pid."
p ->
p
end
end
@doc ~S"""
Creates a complete overview of process stats based on the given `pid`.
Including but not limited to:
- `id`, the process pid
- `name`, the registered name or `nil`.
- `init`, initial function or name.
- `current`, current function.
- `memory`, the total amount of memory used by the process.
- `reductions`, the amount of reductions.
- `message_queue_length`, the amount of unprocessed messages for the process.,
"""
@spec info(
pid :: pid | list | binary | integer | {integer, integer, integer}
) :: :error | map
def info(pid) do
pid
|> pid()
|> process_info(@process_full, &structure_full/2)
end
@doc ~S"""
Retreives a list of process summaries.
Every summary contains:
- `id`, the process pid.
- `name`, the registered name or `nil`.
- `init`, initial function or name.
- `current`, current function.
- `memory`, the total amount of memory used by the process.
- `reductions`, the amount of reductions.
- `message_queue_length`, the amount of unprocessed messages for the process.
"""
@spec list :: list(map)
def list do
:erlang.processes
|> Enum.map(&summary/1)
end
@doc ~S"""
Creates formatted meta information about the process based on the given `pid`.
The information contains:
- `init`, initial function or name.
- `current`, current function.
- `status`, process status.
"""
@spec meta(pid :: pid) :: map
def meta(pid),
do: pid |> process_info(@process_meta, &structure_meta/2)
@doc ~S"""
Creates formatted summary about the process based on the given `pid`.
Every summary contains:
- `id`, the process pid.
- `name`, the registered name or `nil`.
- `init`, initial function or name.
- `current`, current function.
- `memory`, the total amount of memory used by the process.
- `reductions`, the amount of reductions.
- `message_queue_length`, the amount of unprocessed messages for the process.
"""
@spec summary(pid :: pid) :: map
def summary(pid),
do: pid |> process_info(@process_summary, &structure_summary/2)
# Helpers
defp process_info(nil, _, _), do: :error
defp process_info(pid, information, structurer) do
case :erlang.process_info(pid, information) do
:undefined -> :error
data -> structurer.(data, pid)
end
end
defp process_status_module(pid) do
{:status, ^pid, {:module, class}, _} = :sys.get_status(pid, 100)
class
catch
_, _ -> :unknown
end
defp state(pid) do
:sys.get_state(pid, 100)
catch
_, _ -> :unknown
end
@doc false
@spec initial_call(data :: keyword) :: {atom, atom, integer} | atom
def 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_summary(data, pid) do
process_name =
case Keyword.get(data, :registered_name, []) do
[] -> nil
name -> name
end
%{
pid: pid,
name: process_name,
init: format_function(initial_call(data)),
current: format_function(Keyword.get(data, :current_function, nil)),
memory: Keyword.get(data, :memory, 0),
reductions: Keyword.get(data, :reductions, 0),
message_queue_length: Keyword.get(data, :message_queue_len, 0),
}
end
defp structure_full(data, pid) do
gc = Keyword.get(data, :garbage_collection, [])
dictionary = Keyword.get(data, :dictionary)
%{
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),
},
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: to_string(:io_lib.format("~tp", [(state(pid))])),
}
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: format_function(init),
current: format_function(Keyword.get(data, :current_function)),
status: Keyword.get(data, :status),
class: class,
}
end
end