diff --git a/lib/live_debugger_web/components/traces.ex b/lib/live_debugger_web/components/traces.ex index e81c3c03f..fcfd74477 100644 --- a/lib/live_debugger_web/components/traces.ex +++ b/lib/live_debugger_web/components/traces.ex @@ -199,4 +199,49 @@ defmodule LiveDebuggerWeb.Components.Traces do true -> "" end end + + attr(:traces_continuation, :any, required: true) + attr(:tracing_helper, :any, required: true) + + def load_more_button(assigns) do + ~H""" +
+ <.load_more_button_content + traces_continuation={@traces_continuation} + tracing_helper={@tracing_helper} + /> +
+ """ + end + + defp load_more_button_content(%{traces_continuation: nil} = assigns), do: ~H"" + defp load_more_button_content(%{traces_continuation: :end_of_table} = assigns), do: ~H"" + defp load_more_button_content(%{tracing_helper: %{tracing_started?: true}} = assigns), do: ~H"" + + defp load_more_button_content(%{traces_continuation: :loading} = assigns) do + ~H""" + <.spinner size="sm" class="mb-4" /> + """ + end + + defp load_more_button_content(%{traces_continuation: :error} = assigns) do + ~H""" + <.alert + variant="danger" + with_icon={true} + heading="Error while loading more traces" + class="w-full mb-4" + > + Check logs for more details. + + """ + end + + defp load_more_button_content(%{traces_continuation: cont} = assigns) when is_tuple(cont) do + ~H""" + <.button phx-click="load-more" class="w-4 mb-4" variant="secondary"> + Load more + + """ + end end diff --git a/lib/live_debugger_web/helpers/traces_live_helper.ex b/lib/live_debugger_web/helpers/traces_live_helper.ex new file mode 100644 index 000000000..ca98a4db3 --- /dev/null +++ b/lib/live_debugger_web/helpers/traces_live_helper.ex @@ -0,0 +1,76 @@ +defmodule LiveDebuggerWeb.Helpers.TracesLiveHelper do + @moduledoc """ + This module provides helpers for the TracesLive and especially its hooks. + Since these hooks get more complex and they touch different assigns and streams, + this module was created to check if the assigns and streams are present in the socket. + + This way we can catch errors early and not have to debug them in the hooks. + """ + + alias LiveDebugger.Utils.Callbacks, as: UtilsCallbacks + alias LiveDebugger.Structs.TreeNode + + @doc """ + Checks if the assign is present in the socket. + If not, it raises an error. + """ + def check_assign!(socket, assign_name) do + if Map.has_key?(socket.assigns, assign_name) do + socket + else + raise "Assign #{assign_name} is required." + end + end + + @doc """ + Checks if the stream is present in the socket. + If not, it raises an error. + """ + def check_stream!(socket, stream_name) do + if Map.has_key?(socket.assigns.streams, stream_name) do + socket + else + raise "Stream #{stream_name} is required." + end + end + + @doc """ + Returns the default filters for the traces. + """ + def default_filters(node_id) do + functions = + node_id + |> TreeNode.type() + |> case do + :live_view -> UtilsCallbacks.live_view_callbacks() + :live_component -> UtilsCallbacks.live_component_callbacks() + end + |> Enum.map(fn {function, _} -> {function, true} end) + + %{ + functions: functions, + execution_time: [ + {:exec_time_max, ""}, + {:exec_time_min, ""} + ] + } + end + + @doc """ + Returns the execution times for the traces. + """ + def get_execution_times(socket) do + socket.assigns.current_filters.execution_time + |> Enum.filter(fn {_, value} -> value != "" end) + |> Enum.map(fn {filter, value} -> {filter, String.to_integer(value)} end) + end + + @doc """ + Returns the active functions for the traces. + """ + def get_active_functions(socket) do + socket.assigns.current_filters.functions + |> Enum.filter(fn {_, active?} -> active? end) + |> Enum.map(fn {function, _} -> function end) + end +end diff --git a/lib/live_debugger_web/hooks/traces_live/existing_traces.ex b/lib/live_debugger_web/hooks/traces_live/existing_traces.ex new file mode 100644 index 000000000..a092a4afe --- /dev/null +++ b/lib/live_debugger_web/hooks/traces_live/existing_traces.ex @@ -0,0 +1,151 @@ +defmodule LiveDebuggerWeb.Hooks.TracesLive.ExistingTraces do + @moduledoc """ + This hook is responsible for fetching the existing traces and displaying them in the LiveView. + It encapsulates logic for async fetching of traces. + It attaches a hook to the `:existing_traces` stream to handle the async fetching. + + Required assigns (that are used somehow in the hook): + - `:lv_process` - the LiveView process + - `:current_filters` - the current filters + - `:node_id` - the node ID + - `:traces_empty?` - whether the existing traces are empty, possible values: `true`, `false` + + Required stream: + - `:existing_traces` - the stream of existing traces. + + Assigns introduced by this hook (they can be used outside of the hook): + - `:traces_continuation` - the continuation token for the existing traces, possible values: `nil`, `:end_of_table`, `ets_continuation()` + - `:existing_traces_status` - the status of the existing traces, possible values: `:loading`, `:ok`, `:error` + """ + + require Logger + + import Phoenix.LiveView + import Phoenix.Component + import LiveDebuggerWeb.Helpers + import LiveDebuggerWeb.Helpers.TracesLiveHelper + + alias LiveDebugger.Services.TraceService + alias LiveDebugger.Structs.TraceDisplay + + def init_hook(socket, page_size) do + socket + |> check_assign!(:lv_process) + |> check_assign!(:node_id) + |> check_assign!(:current_filters) + |> check_assign!(:traces_empty?) + |> check_stream!(:existing_traces) + |> assign(:traces_continuation, nil) + |> put_private(:page_size, page_size) + |> attach_hook(:existing_traces, :handle_async, &handle_async/3) + end + + @doc """ + It loads asynchronously the existing traces and assigns them to the `:existing_traces` stream. + """ + @spec assign_async_existing_traces(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t() + def assign_async_existing_traces(socket) do + pid = socket.assigns.lv_process.pid + node_id = socket.assigns.node_id + page_size = socket.private.page_size + active_functions = get_active_functions(socket) + execution_times = get_execution_times(socket) + + socket + |> assign(:existing_traces_status, :loading) + |> stream(:existing_traces, [], reset: true) + |> start_async(:fetch_existing_traces, fn -> + TraceService.existing_traces(pid, + node_id: node_id, + limit: page_size, + functions: active_functions, + execution_times: execution_times + ) + end) + end + + @doc """ + It loads asynchronously more existing traces and assigns them to the `:existing_traces` stream. + """ + @spec assign_async_more_existing_traces(Phoenix.LiveView.Socket.t()) :: + Phoenix.LiveView.Socket.t() + def assign_async_more_existing_traces(socket) do + pid = socket.assigns.lv_process.pid + node_id = socket.assigns.node_id + cont = socket.assigns.traces_continuation + page_size = socket.private.page_size + active_functions = get_active_functions(socket) + execution_times = get_execution_times(socket) + + socket + |> assign(:traces_continuation, :loading) + |> start_async(:load_more_existing_traces, fn -> + TraceService.existing_traces(pid, + node_id: node_id, + limit: page_size, + cont: cont, + functions: active_functions, + execution_times: execution_times + ) + end) + end + + defp handle_async(:fetch_existing_traces, {:ok, {trace_list, cont}}, socket) do + trace_list = Enum.map(trace_list, &TraceDisplay.from_trace/1) + + socket + |> assign(:existing_traces_status, :ok) + |> assign(:traces_empty?, false) + |> assign(:traces_continuation, cont) + |> stream(:existing_traces, trace_list) + |> halt() + end + + defp handle_async(:fetch_existing_traces, {:ok, :end_of_table}, socket) do + socket + |> assign(:existing_traces_status, :ok) + |> assign(:traces_continuation, :end_of_table) + |> halt() + end + + defp handle_async(:fetch_existing_traces, {:exit, reason}, socket) do + log_async_error("fetching existing traces", reason) + + socket + |> assign(:existing_traces_status, :error) + |> halt() + end + + defp handle_async(:load_more_existing_traces, {:ok, {trace_list, cont}}, socket) do + trace_list = Enum.map(trace_list, &TraceDisplay.from_trace/1) + + socket + |> assign(:traces_continuation, cont) + |> stream(:existing_traces, trace_list) + |> halt() + end + + defp handle_async(:load_more_existing_traces, {:ok, :end_of_table}, socket) do + socket + |> assign(:traces_continuation, :end_of_table) + |> halt() + end + + defp handle_async(:load_more_existing_traces, {:exit, reason}, socket) do + log_async_error("loading more existing traces", reason) + + socket + |> assign(:traces_continuation, :error) + |> halt() + end + + defp handle_async(_, _, socket) do + {:cont, socket} + end + + defp log_async_error(operation, reason) do + Logger.error( + "LiveDebugger encountered unexpected error while #{operation}: #{inspect(reason)}" + ) + end +end diff --git a/lib/live_debugger_web/hooks/traces_live/incoming_traces.ex b/lib/live_debugger_web/hooks/traces_live/incoming_traces.ex new file mode 100644 index 000000000..f7421c663 --- /dev/null +++ b/lib/live_debugger_web/hooks/traces_live/incoming_traces.ex @@ -0,0 +1,73 @@ +defmodule LiveDebuggerWeb.Hooks.TracesLive.IncomingTraces do + @moduledoc """ + This hook is responsible for handling incoming traces + It is responsible for inserting new traces into the `:existing_traces` stream. + It also handles the case when the trace callback is running + + This hook has to be added after TracingFuse hook - they're both handling `:new_trace` and `:updated_trace` messages. + TracingFuse has to be added first because it's responsible for stopping the trace callback. + + Required assigns (that are used somehow in the hook): + - `:current_filters` - the current filters + - `:traces_empty?` - whether the existing traces are empty, possible values: `true`, `false` + - `:trace_callback_running?` - whether the trace callback is running + + Required stream: + - `:existing_traces` - the stream of existing traces. + """ + + import Phoenix.LiveView + import Phoenix.Component + import LiveDebuggerWeb.Helpers + import LiveDebuggerWeb.Helpers.TracesLiveHelper + + alias LiveDebugger.Structs.TraceDisplay + + @live_stream_limit 128 + + def init_hook(socket) do + socket + |> check_assign!(:current_filters) + |> check_assign!(:traces_empty?) + |> check_stream!(:existing_traces) + |> check_assign!(:trace_callback_running?) + |> attach_hook(:incoming_traces, :handle_info, &handle_info/2) + end + + defp handle_info({:new_trace, trace}, socket) do + trace_display = TraceDisplay.from_trace(trace, true) + + socket + |> stream_insert(:existing_traces, trace_display, at: 0, limit: @live_stream_limit) + |> assign(traces_empty?: false) + |> assign(trace_callback_running?: true) + |> halt() + end + + defp handle_info({:updated_trace, trace}, socket) when socket.assigns.trace_callback_running? do + trace_display = TraceDisplay.from_trace(trace, true) + + execution_time = get_execution_times(socket) + min_time = Keyword.get(execution_time, :exec_time_min, 0) + max_time = Keyword.get(execution_time, :exec_time_max, :infinity) + + if trace.execution_time >= min_time and trace.execution_time <= max_time do + socket + |> stream_insert(:existing_traces, trace_display, at: 0, limit: @live_stream_limit) + else + socket + |> stream_delete(:existing_traces, trace_display) + end + |> assign(trace_callback_running?: false) + |> push_event("stop-timer", %{}) + |> halt() + end + + defp handle_info({:updated_trace, _trace}, socket) do + {:halt, socket} + end + + defp handle_info(_, socket) do + {:cont, socket} + end +end diff --git a/lib/live_debugger_web/helpers/tracing_helper.ex b/lib/live_debugger_web/hooks/traces_live/tracing_fuse.ex similarity index 62% rename from lib/live_debugger_web/helpers/tracing_helper.ex rename to lib/live_debugger_web/hooks/traces_live/tracing_fuse.ex index d7a43a67c..bd125b580 100644 --- a/lib/live_debugger_web/helpers/tracing_helper.ex +++ b/lib/live_debugger_web/hooks/traces_live/tracing_fuse.ex @@ -1,26 +1,44 @@ -defmodule LiveDebuggerWeb.Helpers.TracingHelper do +defmodule LiveDebuggerWeb.Hooks.TracesLive.TracingFuse do @moduledoc """ - This module provides a helper to manage tracing. + This hook is responsible for managing the tracing fuse. It is responsible for determining if the tracing should be stopped. It introduces a fuse mechanism to prevent LiveView from being overloaded with traces. + It also handles the case when the trace callback is running. + This hook has to be added before IncomingTraces hook. + + Required assigns (that are used somehow in the hook): + - `:lv_process` - the LiveView process + - `:node_id` - the node ID + - `:current_filters` - the current filters + - `:root_pid` - the root PID + - `:trace_callback_running?` - whether the trace callback is running """ import Phoenix.Component, only: [assign: 3] + import LiveDebuggerWeb.Helpers + import Phoenix.LiveView + import LiveDebuggerWeb.Helpers.TracesLiveHelper alias Phoenix.LiveView.Socket alias LiveDebugger.Utils.PubSub, as: PubSubUtils + alias LiveDebuggerWeb.Hooks.Flash + alias LiveDebugger.Utils.Parsers @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) + @spec init_hook(Socket.t()) :: Socket.t() + def init_hook(socket) do + socket + |> check_assign!(:lv_process) + |> check_assign!(:node_id) + |> check_assign!(:current_filters) + |> check_assign!(:trace_callback_running?) + |> check_assign!(:root_pid) + |> attach_hook(:tracing_helper, :handle_info, &handle_info/2) + |> clear_tracing() end @spec switch_tracing(Socket.t()) :: Socket.t() @@ -37,8 +55,40 @@ defmodule LiveDebuggerWeb.Helpers.TracingHelper do clear_tracing(socket) end - @spec maybe_disable_tracing_after_update(Socket.t()) :: Socket.t() - def maybe_disable_tracing_after_update(socket) do + defp handle_info({:new_trace, _}, socket) do + socket + |> check_fuse() + |> case do + {:ok, socket} -> + {:cont, socket} + + {:stopped, socket} -> + limit = @trace_limit_per_period + period = @time_period |> Parsers.parse_elapsed_time() + + socket.assigns.root_pid + |> Flash.push_flash( + socket, + "Callback tracer stopped: Too many callbacks in a short time. Current limit is #{limit} callbacks in #{period}." + ) + |> halt() + + {_, socket} -> + {:halt, socket} + end + end + + defp handle_info({:updated_trace, _}, socket) when socket.assigns.trace_callback_running? do + socket + |> maybe_disable_tracing_after_update() + |> cont() + end + + defp handle_info(_, socket) do + {:cont, socket} + end + + defp maybe_disable_tracing_after_update(socket) do if socket.assigns[@assign_name].tracing_started? do socket else @@ -46,17 +96,11 @@ defmodule LiveDebuggerWeb.Helpers.TracingHelper do end 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 + defp check_fuse(%{assigns: %{@assign_name => %{tracing_started?: false}}} = socket) do {:noop, socket} end - def check_fuse(%{assigns: %{@assign_name => %{tracing_started?: true}}} = socket) do + defp check_fuse(%{assigns: %{@assign_name => %{tracing_started?: true}}} = socket) do fuse = socket.assigns[@assign_name].fuse cond do diff --git a/lib/live_debugger_web/live/global_traces_live.ex b/lib/live_debugger_web/live/global_traces_live.ex index 5ede0e3c6..ce13531ad 100644 --- a/lib/live_debugger_web/live/global_traces_live.ex +++ b/lib/live_debugger_web/live/global_traces_live.ex @@ -21,7 +21,6 @@ defmodule LiveDebuggerWeb.GlobalTracesLive do class="sm:hidden" /> -
diff --git a/lib/live_debugger_web/live/traces_live.ex b/lib/live_debugger_web/live/traces_live.ex index 5c3332563..dffdee161 100644 --- a/lib/live_debugger_web/live/traces_live.ex +++ b/lib/live_debugger_web/live/traces_live.ex @@ -7,16 +7,17 @@ defmodule LiveDebuggerWeb.TracesLive do require Logger - alias LiveDebuggerWeb.Helpers.TracingHelper alias LiveDebugger.Services.TraceService alias LiveDebugger.Structs.TraceDisplay alias LiveDebugger.Utils.PubSub, as: PubSubUtils - alias LiveDebugger.Utils.Callbacks, as: UtilsCallbacks - alias LiveDebugger.Utils.Parsers - alias LiveDebugger.Structs.TreeNode alias LiveDebuggerWeb.Components.Traces - @live_stream_limit 128 + alias LiveDebuggerWeb.Hooks.TracesLive.ExistingTraces + alias LiveDebuggerWeb.Hooks.TracesLive.IncomingTraces + alias LiveDebuggerWeb.Hooks.TracesLive.TracingFuse + + import LiveDebuggerWeb.Helpers.TracesLiveHelper + @page_size 25 @separator %{id: "separator"} @@ -61,18 +62,20 @@ defmodule LiveDebuggerWeb.TracesLive do default_filters = default_filters(node_id) socket - |> assign(:displayed_trace, nil) - |> assign(:traces_continuation, nil) |> assign(current_filters: default_filters) - |> assign(default_filters: default_filters) + |> assign(lv_process: lv_process) + |> assign(node_id: node_id) |> assign(traces_empty?: true) |> assign(trace_callback_running?: false) - |> assign(node_id: node_id) - |> assign(id: session["id"]) + |> stream(:existing_traces, []) |> assign(root_pid: session["root_pid"]) - |> assign(lv_process: lv_process) - |> TracingHelper.init() - |> assign_async_existing_traces() + |> TracingFuse.init_hook() + |> ExistingTraces.init_hook(@page_size) + |> IncomingTraces.init_hook() + |> assign(:displayed_trace, nil) + |> assign(default_filters: default_filters) + |> assign(id: session["id"]) + |> ExistingTraces.assign_async_existing_traces() |> ok() end @@ -140,20 +143,10 @@ defmodule LiveDebuggerWeb.TracesLive do <% end %> <% end %>
-
- <%= if @traces_continuation != :loading do %> - <.button - :if={not @tracing_helper.tracing_started? && @traces_continuation != :end_of_table} - phx-click="load-more" - class="w-4 mb-4" - variant="secondary" - > - Load more - - <% else %> - <.spinner size="sm" class="mb-4" /> - <% end %> -
+ @@ -161,124 +154,16 @@ defmodule LiveDebuggerWeb.TracesLive do """ end - @impl true - def handle_async(:fetch_existing_traces, {:ok, {trace_list, cont}}, socket) do - trace_list = Enum.map(trace_list, &TraceDisplay.from_trace/1) - - socket - |> assign(existing_traces_status: :ok) - |> assign(:traces_empty?, false) - |> assign(:traces_continuation, cont) - |> stream(:existing_traces, trace_list) - |> noreply() - end - - @impl true - def handle_async(:fetch_existing_traces, {:ok, :end_of_table}, socket) do - socket - |> assign(existing_traces_status: :ok) - |> assign(traces_continuation: :end_of_table) - |> noreply() - end - - @impl true - def handle_async(:fetch_existing_traces, {:exit, reason}, socket) do - log_async_error("fetching existing traces", reason) - - socket - |> assign(existing_traces_status: :error) - |> noreply() - end - - @impl true - def handle_async(:load_more_existing_traces, {:ok, {trace_list, cont}}, socket) do - trace_list = Enum.map(trace_list, &TraceDisplay.from_trace/1) - - socket - |> assign(:traces_continuation, cont) - |> stream(:existing_traces, trace_list) - |> noreply() - end - - @impl true - def handle_async(:load_more_existing_traces, {:ok, :end_of_table}, socket) do - socket - |> assign(:traces_continuation, :end_of_table) - |> noreply() - end - - @impl true - def handle_async(:load_more_existing_traces, {:exit, reason}, socket) do - log_async_error("loading more existing traces", reason) - socket - end - - @impl true - def handle_info({:new_trace, trace}, socket) do - socket - |> TracingHelper.check_fuse() - |> case do - {:ok, socket} -> - trace_display = TraceDisplay.from_trace(trace, true) - - socket - |> stream_insert(:existing_traces, trace_display, at: 0, limit: @live_stream_limit) - |> assign(traces_empty?: false) - |> assign(trace_callback_running?: true) - - {:stopped, socket} -> - limit = TracingHelper.trace_limit_per_period() - period = TracingHelper.time_period() |> Parsers.parse_elapsed_time() - - socket.assigns.root_pid - |> push_flash( - socket, - "Callback tracer stopped: Too many callbacks in a short time. Current limit is #{limit} callbacks in #{period}." - ) - - {_, socket} -> - socket - end - |> noreply() - end - - @impl true - def handle_info({:updated_trace, trace}, socket) when socket.assigns.trace_callback_running? do - trace_display = TraceDisplay.from_trace(trace, true) - - execution_time = get_execution_times(socket) - min_time = Keyword.get(execution_time, :exec_time_min, 0) - max_time = Keyword.get(execution_time, :exec_time_max, :infinity) - - if trace.execution_time >= min_time and trace.execution_time <= max_time do - socket - |> stream_insert(:existing_traces, trace_display, at: 0, limit: @live_stream_limit) - else - socket - |> stream_delete(:existing_traces, trace_display) - end - |> assign(trace_callback_running?: false) - |> TracingHelper.maybe_disable_tracing_after_update() - |> push_event("stop-timer", %{}) - |> noreply() - end - - @impl true - def handle_info({:updated_trace, _trace}, socket) do - socket - |> noreply() - end - @impl true def handle_info({:node_changed, node_id}, socket) do default_filters = default_filters(node_id) socket - |> TracingHelper.disable_tracing() + |> TracingFuse.disable_tracing() |> assign(node_id: node_id) |> assign(current_filters: default_filters) |> assign(default_filters: default_filters) - |> assign_async_existing_traces() + |> ExistingTraces.assign_async_existing_traces() |> noreply() end @@ -289,13 +174,13 @@ defmodule LiveDebuggerWeb.TracesLive do socket |> assign(:current_filters, filters) |> assign(:traces_empty?, true) - |> assign_async_existing_traces() + |> ExistingTraces.assign_async_existing_traces() |> noreply() end @impl true def handle_event("switch-tracing", _, socket) do - socket = TracingHelper.switch_tracing(socket) + socket = TracingFuse.switch_tracing(socket) if socket.assigns.tracing_helper.tracing_started? and !socket.assigns.traces_empty? do socket @@ -310,7 +195,7 @@ defmodule LiveDebuggerWeb.TracesLive do @impl true def handle_event("load-more", _, socket) do socket - |> load_more_existing_traces() + |> ExistingTraces.assign_async_more_existing_traces() |> noreply() end @@ -369,93 +254,7 @@ defmodule LiveDebuggerWeb.TracesLive do @impl true def handle_event("refresh-history", _, socket) do socket - |> assign_async_existing_traces() + |> ExistingTraces.assign_async_existing_traces() |> noreply() end - - defp assign_async_existing_traces(socket) do - pid = socket.assigns.lv_process.pid - node_id = socket.assigns.node_id - active_functions = get_active_functions(socket) - execution_times = get_execution_times(socket) - - socket - |> assign(:existing_traces_status, :loading) - |> stream(:existing_traces, [], reset: true) - |> start_async(:fetch_existing_traces, fn -> - TraceService.existing_traces(pid, - node_id: node_id, - limit: @page_size, - functions: active_functions, - execution_times: execution_times - ) - end) - end - - defp load_more_existing_traces(socket) do - pid = socket.assigns.lv_process.pid - node_id = socket.assigns.node_id - cont = socket.assigns.traces_continuation - active_functions = get_active_functions(socket) - execution_times = get_execution_times(socket) - - socket - |> assign(:traces_continuation, :loading) - |> start_async(:load_more_existing_traces, fn -> - TraceService.existing_traces(pid, - node_id: node_id, - limit: @page_size, - cont: cont, - functions: active_functions, - execution_times: execution_times - ) - end) - end - - defp default_filters(node_id) do - functions = - node_id - |> TreeNode.type() - |> case do - :live_view -> UtilsCallbacks.live_view_callbacks() - :live_component -> UtilsCallbacks.live_component_callbacks() - end - |> Enum.map(fn {function, _} -> {function, true} end) - - %{ - functions: functions, - execution_time: [ - {:exec_time_max, ""}, - {:exec_time_min, ""}, - {:min_unit, ""}, - {:max_unit, ""} - ] - } - end - - defp get_active_functions(socket) do - socket.assigns.current_filters.functions - |> Enum.filter(fn {_, active?} -> active? end) - |> Enum.map(fn {function, _} -> function end) - end - - defp get_execution_times(socket) do - execution_time = socket.assigns.current_filters.execution_time - - execution_time - |> Enum.filter(fn {_, value} -> value not in ["" | Parsers.time_units()] end) - |> Enum.map(fn {filter, value} -> {filter, String.to_integer(value)} end) - |> Enum.map(fn {filter, value} -> - case filter do - :exec_time_min -> {filter, Parsers.time_to_microseconds(value, execution_time[:min_unit])} - :exec_time_max -> {filter, Parsers.time_to_microseconds(value, execution_time[:max_unit])} - end - end) - end - - defp log_async_error(operation, reason) do - Logger.error( - "LiveDebugger encountered unexpected error while #{operation}: #{inspect(reason)}" - ) - end end