lib/step_flow/repo/supervision_tree.ex

defmodule StepFlow.Repo.SupervisionTree do
  @moduledoc """
  Dump the application supervision tree into the application logs.
  Useful for debugging purpose.
  """

  use GenServer

  require Logger

  @interval 10_000

  def child_spec(_) do
    %{
      id: StepFlow.Repo.SupervisionTree,
      start: {StepFlow.Repo.SupervisionTree, :start_link, []},
      type: :worker
    }
  end

  def start_link do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  @impl true
  def init(_) do
    Logger.warn("[#{__MODULE__}] Start printing supervision tree!")

    tree =
      "\n" <>
        print_supervision_tree(StepFlow.Repo) <>
        print_supervision_tree(StepFlow.ProcessManager) <>
        print_supervision_tree(StepFlow.Amqp.Supervisor)

    Logger.info("[#{__MODULE__}] Supervision tree:\n#{tree}")

    Process.send_after(self(), :print, @interval)

    {:ok, %{}}
  end

  @impl true
  def handle_info(:print, _) do
    tree =
      "\n" <>
        print_supervision_tree(StepFlow.Repo) <>
        print_supervision_tree(StepFlow.ProcessManager) <>
        print_supervision_tree(StepFlow.Amqp.Supervisor)

    Logger.info("[#{__MODULE__}] Supervision tree:\n#{tree}")

    Process.send_after(self(), :print, @interval)

    {:noreply, %{}}
  end

  defp print_supervision_tree(supervisor) do
    case Process.whereis(supervisor) do
      nil ->
        "\t==== Supervision tree from #{inspect(supervisor)}: not running ====\n\n"

      process_manager_pid ->
        "\t==== Supervision tree from: #{inspect(supervisor)} (#{inspect(process_manager_pid)}) ====\n" <>
          if Process.alive?(process_manager_pid) do
            subtree =
              Supervisor.which_children(supervisor)
              |> print_children()
              |> Enum.join("\n")

            subtree <> "\n\n"
          else
            "\t#{inspect(supervisor)}: is dead (#{inspect(process_manager_pid)})\n\n"
          end
    end
  end

  defp print_children(_children, _tab \\ 1, _result \\ [])
  defp print_children([], _tab, result), do: result

  defp print_children([child | children], tab, result) do
    {name, pid, type, _} = child

    pid_str =
      case pid do
        :restarting ->
          ":restarting"

        :undefined ->
          ":undefined"

        pid ->
          status =
            if Process.alive?(pid) do
              "alive"
            else
              "dead"
            end

          "#{inspect(pid)} (#{status})"
      end

    result =
      result ++
        [
          String.duplicate("\t", tab) <>
            " |- #{String.pad_trailing("#{name}", 50, " ")}\t #{pid_str}\t #{inspect(type)}"
        ]

    result =
      result ++
        if type == :supervisor and pid != :restarting and pid != :undefined and
             Process.alive?(pid) do
          Supervisor.which_children(name)
          |> print_children(tab + 1)
        else
          []
        end

    print_children(children, tab, result)
  end
end