Current section
Files
Jump to
Current section
Files
lib/live_debugger_web/helpers/tracing_helper.ex
defmodule LiveDebuggerWeb.Helpers.TracingHelper do
@moduledoc """
This module provides a helper to manage tracing.
It is responsible for determining if the tracing should be stopped.
It introduces a fuse mechanism to prevent LiveView from being overloaded with traces.
"""
import Phoenix.Component, only: [assign: 3]
alias Phoenix.LiveView.Socket
alias LiveDebugger.Utils.PubSub, as: PubSubUtils
@assign_name :tracing_helper
@time_period 1_000_000
@trace_limit_per_period 100
def trace_limit_per_period(), do: @trace_limit_per_period
def time_period(), do: @time_period
@spec init(Socket.t()) :: Socket.t()
def init(socket) do
clear_tracing(socket)
end
@spec switch_tracing(Socket.t()) :: Socket.t()
def switch_tracing(socket) do
if socket.assigns[@assign_name].tracing_started? do
clear_tracing(socket)
else
start_tracing(socket)
end
end
@spec disable_tracing(Socket.t()) :: Socket.t()
def disable_tracing(socket) do
clear_tracing(socket)
end
@doc """
Checks if the fuse is blown and stops tracing if it is.
It uses the `#{@assign_name}` assign to store information.
When tracing is not started returns `{:noop, socket}`.
"""
@spec check_fuse(Socket.t()) :: {:ok | :stopped | :noop, Socket.t()}
def check_fuse(%{assigns: %{@assign_name => %{tracing_started?: false}}} = socket) do
{:noop, socket}
end
def check_fuse(%{assigns: %{@assign_name => %{tracing_started?: true}}} = socket) do
fuse = socket.assigns[@assign_name].fuse
cond do
period_exceeded?(fuse) -> {:ok, reset_fuse(socket)}
count_exceeded?(fuse) -> {:stopped, clear_tracing(socket)}
true -> {:ok, increment_fuse(socket)}
end
end
defp period_exceeded?(fuse) do
now() - fuse.start_time >= @time_period
end
defp count_exceeded?(fuse) do
fuse.count + 1 >= @trace_limit_per_period
end
defp increment_fuse(socket) do
fuse = socket.assigns[@assign_name].fuse
assigns = %{
tracing_started?: true,
fuse: %{fuse | count: fuse.count + 1}
}
assign(socket, @assign_name, assigns)
end
defp reset_fuse(socket) do
assigns = %{
tracing_started?: true,
fuse: %{count: 0, start_time: now()}
}
assign(socket, @assign_name, assigns)
end
defp start_tracing(socket) do
assigns = %{
tracing_started?: true,
fuse: %{count: 0, start_time: now()}
}
if Phoenix.LiveView.connected?(socket) && socket.assigns[:lv_process] do
socket
|> get_active_topics()
|> PubSubUtils.subscribe!()
end
assign(socket, @assign_name, assigns)
end
defp clear_tracing(socket) do
assigns = %{
tracing_started?: false,
fuse: nil
}
if Phoenix.LiveView.connected?(socket) && socket.assigns[:lv_process] do
socket
|> get_topics()
|> PubSubUtils.unsubscribe()
end
assign(socket, @assign_name, assigns)
end
defp now() do
:os.system_time(:microsecond)
end
defp get_active_topics(socket) do
lv_process = socket.assigns.lv_process
node_id = socket.assigns.node_id
socket.assigns.current_filters
|> Enum.filter(fn {_, active?} -> active? end)
|> Enum.map(fn {function, _} ->
PubSubUtils.tsnf_topic(lv_process.socket_id, lv_process.transport_pid, node_id, function)
end)
end
defp get_topics(socket) do
lv_process = socket.assigns.lv_process
node_id = socket.assigns.node_id
socket.assigns.current_filters
|> Enum.map(fn {function, _} ->
PubSubUtils.tsnf_topic(lv_process.socket_id, lv_process.transport_pid, node_id, function)
end)
end
end