lib/finch/http2/pool_metrics.ex

defmodule Finch.HTTP2.PoolMetrics do
  @moduledoc """
  HTTP2 Pool metrics.

  Available metrics:

    * `:pool_index` - Index of the pool
    * `:in_flight_requests` - Number of requests currently on the connection

  Caveats:

    * HTTP2 pools have only one connection and leverage the multiplex nature
    of the protocol. That's why we only keep the in flight requests, representing
    the number of streams currently running on the connection.
  """
  @type t :: %__MODULE__{}

  defstruct [
    :pool_index,
    :in_flight_requests
  ]

  @atomic_idx [
    pool_idx: 1,
    in_flight_requests: 2
  ]

  def init(finch_name, shp, pool_idx) do
    ref = :atomics.new(length(@atomic_idx), [])
    :atomics.put(ref, @atomic_idx[:pool_idx], pool_idx)

    :persistent_term.put({__MODULE__, finch_name, shp, pool_idx}, ref)
    {:ok, ref}
  end

  def maybe_add(nil, _metrics_list), do: :ok

  def maybe_add(ref, metrics_list) do
    Enum.each(metrics_list, fn {metric_name, val} ->
      :atomics.add(ref, @atomic_idx[metric_name], val)
    end)
  end

  def get_pool_status(name, shp, pool_idx) do
    {__MODULE__, name, shp, pool_idx}
    |> :persistent_term.get(nil)
    |> get_pool_status()
  end

  def get_pool_status(nil), do: {:error, :not_found}

  def get_pool_status(ref) do
    %{
      pool_idx: pool_idx,
      in_flight_requests: in_flight_requests
    } =
      @atomic_idx
      |> Enum.map(fn {k, idx} -> {k, :atomics.get(ref, idx)} end)
      |> Map.new()

    result = %__MODULE__{
      pool_index: pool_idx,
      in_flight_requests: in_flight_requests
    }

    {:ok, result}
  end
end