Current section
Files
Jump to
Current section
Files
lib/observer_web/apps.ex
defmodule ObserverWeb.Apps do
@moduledoc """
This module will provide observability for the applications processes and ports
## References:
* https://github.com/shinyscorpion/wobserver
"""
alias ObserverWeb.Apps.Helper
alias ObserverWeb.Rpc
@link_line_color "#CCC"
@monitor_line_color "#D1A1E5"
@monitored_by_line_color "#4DB8FF"
@process_symbol "emptycircle"
@process_item_color "#93C5FD"
@app_process_symbol "emptydiamond"
@app_process_item_color "#A1887F"
@supervisor_symbol "emptyroundRect"
@supervisor_item_color "#F87171"
@port_symbol "emptytriangle"
@port_item_color "#FBBF24"
@reference_symbol "emptyrect"
@reference_item_color "#28A745"
@type t :: %__MODULE__{
id: pid() | port() | reference() | nil,
children: list(),
name: String.t(),
symbol: String.t(),
lineStyle: map(),
itemStyle: map()
}
@derive Jason.Encoder
defstruct id: nil,
children: [],
name: "",
symbol: @process_symbol,
lineStyle: %{color: @link_line_color},
itemStyle: %{color: @process_item_color}
@doc """
Lists all running applications.
"""
@spec list(node :: atom()) :: list({atom, String.t(), String.t()})
def list(node \\ Node.self()) do
Rpc.call(node, :application_controller, :which_applications, [], :infinity)
|> Enum.filter(&alive?(node, &1))
|> Enum.map(&structure_application/1)
end
@doc """
Retrieves information about the application and its respective linked processes, ports and references.
"""
@spec info(node :: atom(), app :: atom) :: map
def info(node \\ Node.self(), app \\ :kernel) do
app_pid = Rpc.call(node, :application_controller, :get_master, [app], :infinity)
children =
node
|> Rpc.call(:application_master, :get_child, [app_pid], :infinity)
|> structure_id(app_pid)
new(%{
id: app_pid,
children: children,
symbol: @app_process_symbol,
itemStyle: %{color: @app_process_item_color}
})
end
### ==========================================================================
### Private functions
### ==========================================================================
defp alive?(node, {app, _, _}) do
node
|> Rpc.call(:application_controller, :get_master, [app], :infinity)
|> is_pid
catch
# coveralls-ignore-start
_, _ ->
false
# coveralls-ignore-stop
end
defp structure_application({name, description, version}) do
%{
name: name,
description: to_string(description),
version: to_string(version)
}
end
defp structure_id({pid, name}, parent) do
{_, dictionary} = Rpc.pinfo(pid, :dictionary)
case Keyword.get(dictionary, :"$ancestors") do
[ancestor_parent] ->
child = structure_id({name, pid, :supervisor, []}, ancestor_parent)
[
new(%{
id: ancestor_parent,
children: [child],
symbol: @app_process_symbol,
itemStyle: %{color: @app_process_item_color}
})
]
_ ->
# coveralls-ignore-start
child = structure_id({name, pid, :supervisor, []}, parent)
[child]
# coveralls-ignore-stop
end
end
defp structure_id({_, :undefined, _, _}, _parent), do: nil
defp structure_id({_, pid, :supervisor, _}, parent) do
{:links, links} = Rpc.pinfo(pid, :links)
links = links -- [parent]
children =
pid
|> :supervisor.which_children()
|> Kernel.++(Enum.filter(links, fn link -> is_port(link) end))
|> Helper.parallel_map(&structure_id(&1, pid))
|> Enum.filter(&(&1 != nil))
new(%{
id: pid,
children: children,
symbol: @supervisor_symbol,
itemStyle: %{color: @supervisor_item_color}
})
end
defp structure_id({_, pid, :worker, _}, parent) do
{:links, links} = Rpc.pinfo(pid, :links)
{:monitored_by, monitored_by_pids} = Rpc.pinfo(pid, :monitored_by)
{:monitors, monitors} = Rpc.pinfo(pid, :monitors)
links = links -- [parent]
children = Enum.map(links, &structure_links(&1))
monitored_by_pids = Enum.map(monitored_by_pids, &monitored_by(&1))
monitors = Enum.map(monitors, &monitor(&1))
new(%{
id: pid,
children: children ++ monitored_by_pids ++ monitors
})
end
# coveralls-ignore-start
defp structure_id(id, _parent) when is_port(id) do
new(%{id: id, symbol: @port_symbol})
end
defp structure_id(id, _parent) when is_reference(id) do
new(%{id: id, symbol: @reference_symbol})
end
defp structure_id(id, _parent) do
new(%{id: id})
end
# coveralls-ignore-stop
# Check https://www.erlang.org/docs/26/man/erlang#process_info-2
# coveralls-ignore-start
defp monitored_by(reference) when is_reference(reference) do
new(%{
id: reference,
symbol: @reference_symbol,
itemStyle: %{color: @reference_item_color},
lineStyle: %{color: @monitored_by_line_color}
})
end
defp monitored_by(port) when is_port(port) do
new(%{
id: port,
symbol: @port_symbol,
itemStyle: %{color: @port_item_color},
lineStyle: %{color: @monitored_by_line_color}
})
end
# coveralls-ignore-stop
defp monitored_by(pid) when is_pid(pid) do
new(%{
id: pid,
lineStyle: %{color: @monitored_by_line_color}
})
end
# Check https://www.erlang.org/docs/26/man/erlang#process_info-2
# coveralls-ignore-start
defp monitor({:port, port}) do
new(%{
id: port,
lineStyle: %{color: @monitor_line_color}
})
end
# coveralls-ignore-stop
defp monitor({:process, pid}) do
new(%{
id: pid,
lineStyle: %{color: @monitor_line_color}
})
end
# coveralls-ignore-start
defp structure_links(port) when is_port(port) do
new(%{
id: port,
symbol: @port_symbol,
itemStyle: %{color: @port_item_color}
})
end
# coveralls-ignore-stop
defp structure_links(pid) when is_pid(pid) do
new(%{id: pid})
end
# coveralls-ignore-start
defp structure_links(reference) when is_reference(reference) do
new(%{id: reference})
end
# coveralls-ignore-stop
@spec new(map()) :: struct()
def new(%{id: id} = attrs) when is_port(id) or is_pid(id) or is_reference(id) do
name = name(id)
struct(__MODULE__, Map.put(attrs, :name, name))
end
# coveralls-ignore-start
def new(%{id: id} = attrs) do
name = "#{inspect(id)}"
struct(
__MODULE__,
attrs
|> Map.put(:name, name)
|> Map.put(:id, nil)
)
end
# coveralls-ignore-stop
defp name(pid) when is_pid(pid) do
case Rpc.pinfo(pid, :registered_name) do
{_, registered_name} -> to_string(registered_name) |> String.trim_leading("Elixir.")
_ -> pid |> inspect |> String.trim_leading("#PID")
end
end
# coveralls-ignore-start
defp name(port) when is_port(port), do: port |> inspect |> String.trim_leading("#Port")
defp name(reference) when is_reference(reference),
do: reference |> inspect |> String.trim_leading("#Reference")
# coveralls-ignore-stop
end