Powered by AppSignal & Oban Pro

Recorder

record.livemd

Recorder

Recorder Module

defmodule Recorder do
  use GenServer

  @timeout 237

  # interface

  def add_event(pid, descriptor) do
    GenServer.cast(pid, {:add_event, descriptor})
  end

  def start_monitoring(pid) do
    GenServer.cast(pid, {:start_monitoring, self()})
  end

  # callbacks

  @impl true
  def init([filename: filename] = _opts) do
    {:ok, file} = File.open(filename, [:append])
    register_timeout()
    {:ok, [file: file, clients: []]}
  end

  @impl true
  def handle_cast({:add_event, descriptor}, state) do
    file = state[:file]
    tnow = :os.system_time(:milli_seconds) / 1000
    IO.binwrite(file, "#{tnow} event #{descriptor}\n")
    {:noreply, state}
  end

  @impl true
  def handle_cast({:start_monitoring, client}, [file: file, clients: clients] = _state) do
    state = [file: file, clients: [client] ++ clients]
    {:noreply, state}
  end

  @impl true
  def handle_info(:monitor, [file: file, clients: clients] = state) do
    Enum.map(clients, fn client -> IO.binwrite(file, get_stats(client)) end)

    register_timeout()
    {:noreply, state}
  end

  # helpers

  defp register_timeout() do
    Process.send_after(self(), :monitor, @timeout)
  end

  defp get_stats(pid) do
    tnow = :os.system_time(:milli_seconds) / 1000
    info = Process.info(pid)

    # [
    #   status: status,
    #   message_queue_len: queue,
    #   total_heap_size: heap,
    #   stack_size: stack,
    #   reductions: reductions
    # ] = info
    status = info[:status]
    queue = info[:message_queue_len]
    heap = info[:total_heap_size]
    stack = info[:stack_size]
    reductions = info[:reductions]

    "#{tnow} snapshot pid=#{inspect(pid)} status=#{status} queue=#{queue} heap=#{heap} stack=#{stack} reductions=#{reductions}\n"
  end
end

One-Off Test

filename = "recorder_test.log"

Run test:

{:ok, recorder_pid} = GenServer.start_link(Recorder, filename: filename)
Recorder.add_event(recorder_pid, "locationA key-value list")

Continuous Test

Genserver definition:

defmodule Tester do
  use GenServer

  @timeout 1000
  @maxi 100

  # callbacks

  @impl true
  def init(rec) do
    register_timeout()
    Recorder.start_monitoring(rec)
    {:ok, [i: 0, list: [], rec: rec]}
  end

  @impl true
  def handle_info(:emit, i: i, list: list, rec: rec) do
    Recorder.add_event(rec, "Tester.emit i=#{i}")

    {i, list} =
      case i do
        @maxi -> {0, []}
        _ -> {i + 1, [i] ++ list}
      end

    register_timeout()

    {:noreply, [i: i, list: list, rec: rec]}
  end

  # helpers

  defp register_timeout() do
    Process.send_after(self(), :emit, @timeout)
  end
end

Start it:

{:ok, tester_pid} = GenServer.start_link(Tester, recorder_pid)