lib/orion_collector/tracer.ex

defmodule OrionCollector.Tracer do
  use GenServer

  alias OrionCollector.Aggregator

  @moduledoc """
  This is the process that collect the Trace, then dispatch them per aggregator

  There is one Tracer per node, but one Aggregator per MFA being traced.
  """

  def start_tracer(mfa, pid, start_status) do
    Aggregator.start_agg(mfa, pid)
    change_status(start_status)
  end

  @spec change_status(any) :: any
  def change_status(status) do
    GenServer.call(__MODULE__, status)
  end

  def stop(pid) do
    GenServer.stop(pid, :normal, 5_000)
  end

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

  def child_spec(args) do
    %{
      id: OrionCollector.Tracer,
      start: {OrionCollector.Tracer, :start_link, [args]},
      restart: :permanent
    }
  end

  # --PRIVATE--
  @impl true
  def init(_start) do
    running_trace(false)

    initial_state = %{running_status: :paused}

    {:ok, initial_state}
  end

  @impl true
  def handle_call(start_status, _from, state) do
    running_trace(start_status == :running)
    {:reply, :ok, Map.put(state, :running_status, start_status)}
  end

  @impl true
  def handle_info(
        {:trace_ts, _trace_pid, :call, {m, f, args}, _start_time} = trace_msg,
        state
      ) do
    mfarity = {m, f, length(args)}
    GenServer.cast(Aggregator.mfa_to_name(mfarity), trace_msg)
    {:noreply, state}
  end

  @accepted_return_tags [:return_from, :exception_from]

  @impl true
  def handle_info(
        {:trace_ts, _trace_pid, return_tag, mfa, _TraceTerm, _end_time} = trace_msg,
        state
      )
      when return_tag in @accepted_return_tags do
    GenServer.cast(Aggregator.mfa_to_name(mfa), trace_msg)
    {:noreply, state}
  end

  defp running_trace(bool) do
    :erlang.trace(:all, bool, [:call, :arity, :timestamp])
  end
end