defmodule TestcontainerEx.Recon do
@moduledoc """
Debugging helpers powered by `:recon` (Erlang runtime inspection).
This module is only available in `:dev` and `:test` environments.
It provides convenient Elixir wrappers around common `:recon` operations
for inspecting the TestcontainerEx GenServer, processes, and memory.
## Prerequisites
Add `:recon` to your dependencies (already included in dev/test):
{:recon, "~> 2.5", only: [:dev, :test]}
## Usage (from `iex -S mix`)
# Show top processes by memory
TestcontainerEx.Recon.top_processes()
# Inspect the TestcontainerEx GenServer state
TestcontainerEx.Recon.server_state()
# Show process info for the GenServer
TestcontainerEx.Recon.server_info()
"""
@server_name TestcontainerEx.Server
@compile {:no_warn_undefined, [{:recon, :proc_window, 3}, {:recon_alloc, :memory, 1}]}
@doc """
Returns the current state of the TestcontainerEx GenServer.
Useful for debugging from IEx to see tracked containers, networks,
and compose environments.
"""
@spec server_state() :: map() | {:error, :not_running}
def server_state do
case Process.whereis(@server_name) do
nil -> {:error, :not_running}
pid -> :sys.get_state(pid)
end
end
@doc """
Returns process info for the TestcontainerEx GenServer.
"""
@spec server_info() :: [{atom(), term()}] | {:error, :not_running}
def server_info do
case Process.whereis(@server_name) do
nil -> {:error, :not_running}
pid -> Process.info(pid)
end
end
@doc """
Returns the top N processes sorted by memory usage.
Uses `:recon.proc_windows/2` when available, falls back to a simple
implementation otherwise.
"""
@spec top_processes(pos_integer()) :: [{pid(), term()}]
def top_processes(n \\ 10) when is_integer(n) and n > 0 do
:recon.proc_window(:memory, n, 1)
rescue
_ ->
# Fallback if recon isn't loaded
Process.list()
|> Enum.map(fn pid -> {pid, Process.info(pid, :memory)} end)
|> Enum.filter(fn {_, info} -> info != nil end)
|> Enum.sort_by(fn {_, {:memory, m}} -> m end, :desc)
|> Enum.take(n)
end
@doc """
Returns memory allocation breakdown for the node.
"""
@spec memory() :: [{atom(), non_neg_integer()}]
def memory do
:recon_alloc.memory(:used)
rescue
_ -> :erlang.memory()
end
@doc """
Returns a summary of the TestcontainerEx server's tracked resources.
More readable than `server_state/0` — shows counts and names rather
than the full state struct.
"""
@spec resource_summary() :: map() | {:error, :not_running}
def resource_summary do
case server_state() do
{:error, _} = err ->
err
state ->
%{
containers: MapSet.to_list(state.containers),
container_count: MapSet.size(state.containers),
networks: MapSet.to_list(state.networks),
network_count: MapSet.size(state.networks),
images: MapSet.to_list(state.images),
image_count: MapSet.size(state.images),
compose_env_count: length(state.compose_envs),
connected: !is_nil(state.conn),
docker_hostname: state.docker_hostname,
use_container_ip: state.use_container_ip
}
end
end
end