-
Notifications
You must be signed in to change notification settings - Fork 20
Refactor: Create TraceHandler GenServer #611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
66 commits
Select commit
Hold shift + click to select a range
ac4499b
Added base for TracingManager
kraleppa 60ec4c7
Added callbacks utils
kraleppa be9f0af
Added callbacks query
kraleppa 08c8c50
Tracing setup
kraleppa 8371428
Added process link
kraleppa 4e2e13f
Changed directory name from Receivers to GenServers
kraleppa a4c4cc1
Added events
kraleppa 1a075c0
Extended bus
kraleppa e7ed45c
Added subscription to bus
kraleppa daaeef3
Proper handling in tracing manger events
kraleppa d40a65f
Extracted some code
kraleppa 341a8c6
Extracted tracer function
kraleppa 565f900
Updated tracing manager
kraleppa dfbe1c7
Added test case
kraleppa 68fcd0b
Added tests for queries
kraleppa 4f7da32
Cleanup
kraleppa 8bf2e3b
Removed comments
kraleppa 79ec457
Removed dbgs
kraleppa 8744ae7
Removed TODO
kraleppa 29f734d
Added GenServer tests
kraleppa 61fdf17
Added config checks
kraleppa 91a552d
Update lib/live_debugger_refactor/services/callback_tracer/process/tr…
kraleppa c0ddc46
Update lib/live_debugger_refactor/services/callback_tracer/gen_server…
kraleppa 1cb2870
Update lib/live_debugger_refactor/services/callback_tracer/gen_server…
kraleppa 7e23169
Deleted tests
kraleppa b903cd1
Added `SettingsStorage` init
kraleppa f20441b
Added `delete_component` trace
kraleppa 1b3851a
CR suggestions
kraleppa 5d7b46d
Added base for trace_handler
kraleppa ce3e2c0
Changed names
kraleppa 0ccfe78
Added missing stuff
kraleppa dd9f4ec
Merge branch 'main' into 585-create-tracehandler-service
kraleppa 69a88ac
Format
kraleppa d48ca52
Fixed tests
kraleppa 0645bc7
Basic trace pattern matching added
kraleppa 4e466e8
Updated handlers
kraleppa 6e77147
Changed handlers
kraleppa fbc996b
Added comments
kraleppa a177689
Added events
kraleppa 8addb1d
Init traces storage
kraleppa 1cb00a3
Handled call trace
kraleppa ba998d5
Return trace handling
kraleppa ece5c42
Fixed error with no type
kraleppa 862d1fe
Almost working
kraleppa 79f8be9
Give away trace ets tables
kraleppa ac1ae43
GiveAway ets table to LiveDebugger supervisor
kraleppa faaead1
Cleanup
kraleppa 93975ea
Publishing component deleted trace
kraleppa c40d763
Handled recompile case
kraleppa 76074ea
Checkout config
kraleppa 6f02b17
Merge branch 'main' into 585-create-tracehandler-service
kraleppa 5bd260c
Fixed credo
kraleppa 3bc78dc
Fixed warning
kraleppa 01bb963
Removed `as:`
kraleppa b1940d6
Added `nil` to ref for events
kraleppa cd139ab
Fixed tests
kraleppa d96ce91
Added typedocs
kraleppa 7274a0d
Added specs
kraleppa 6189377
Fixed timestamp
kraleppa eb55126
Merge branch 'main' into 585-create-tracehandler-service
kraleppa db55844
Changed name
kraleppa f4b73c9
Update lib/live_debugger_refactor/api/system/process.ex
kraleppa ddfea01
Fixed comment
kraleppa f5d8d06
Added actions test
kraleppa d8bdc1d
Removed obsolete mock
kraleppa 04f1db3
Revert "Removed obsolete mock"
kraleppa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
lib/live_debugger_refactor/services/callback_tracer/actions/trace.ex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| defmodule LiveDebuggerRefactor.Services.CallbackTracer.Actions.Trace do | ||
| @moduledoc """ | ||
| This module provides actions for traces. | ||
| """ | ||
|
|
||
| alias LiveDebuggerRefactor.Structs.Trace | ||
| alias LiveDebuggerRefactor.API.TracesStorage | ||
| alias LiveDebuggerRefactor.API.LiveViewDebug | ||
|
|
||
| alias LiveDebuggerRefactor.Bus | ||
| alias LiveDebuggerRefactor.Services.CallbackTracer.Events.TraceCalled | ||
| alias LiveDebuggerRefactor.Services.CallbackTracer.Events.TraceReturned | ||
| alias LiveDebuggerRefactor.Services.CallbackTracer.Events.TraceErrored | ||
|
|
||
| @spec create_trace( | ||
| n :: non_neg_integer(), | ||
| module :: module(), | ||
| fun :: atom(), | ||
| args :: list(), | ||
| pid :: pid(), | ||
| timestamp :: :erlang.timestamp() | ||
| ) :: {:ok, Trace.t()} | {:error, term()} | ||
| def create_trace(n, module, fun, args, pid, timestamp) do | ||
| trace = Trace.new(n, module, fun, args, pid, timestamp) | ||
|
|
||
| case trace.transport_pid do | ||
| nil -> | ||
| {:error, "Transport PID is nil"} | ||
|
|
||
| _ -> | ||
| {:ok, trace} | ||
| end | ||
| end | ||
|
|
||
| @spec create_delete_component_trace( | ||
| n :: non_neg_integer(), | ||
| args :: list(), | ||
| pid :: pid(), | ||
| cid :: String.t(), | ||
| timestamp :: :erlang.timestamp() | ||
| ) :: {:ok, Trace.t()} | {:error, term()} | ||
| def create_delete_component_trace(n, args, pid, cid, timestamp) do | ||
| pid | ||
| |> LiveViewDebug.socket() | ||
| |> case do | ||
| {:ok, %{id: socket_id, transport_pid: t_pid}} when is_pid(t_pid) -> | ||
| trace = | ||
| Trace.new( | ||
| n, | ||
| Phoenix.LiveView.Diff, | ||
| :delete_component, | ||
| args, | ||
| pid, | ||
| timestamp, | ||
| socket_id: socket_id, | ||
| transport_pid: t_pid, | ||
| cid: %Phoenix.LiveComponent.CID{cid: cid} | ||
| ) | ||
|
|
||
| {:ok, trace} | ||
|
|
||
| _ -> | ||
| {:error, "Could not get socket"} | ||
| end | ||
| end | ||
|
|
||
| @spec update_trace(Trace.t(), map()) :: {:ok, Trace.t()} | ||
| def update_trace(%Trace{} = trace, params) do | ||
kraleppa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| {:ok, Map.merge(trace, params)} | ||
| end | ||
|
|
||
| @spec persist_trace(Trace.t()) :: {:ok, reference()} | {:error, term()} | ||
| def persist_trace(%Trace{pid: pid} = trace) do | ||
| with ref when is_reference(ref) <- TracesStorage.get_table(pid), | ||
| true <- TracesStorage.insert!(ref, trace) do | ||
| {:ok, ref} | ||
| else | ||
| _ -> | ||
| {:error, "Could not persist trace"} | ||
| end | ||
| end | ||
|
|
||
| @spec persist_trace(Trace.t(), reference()) :: {:ok, reference()} | ||
| def persist_trace(%Trace{} = trace, ref) do | ||
| TracesStorage.insert!(ref, trace) | ||
|
|
||
| {:ok, ref} | ||
| end | ||
|
|
||
| @spec publish_trace(Trace.t(), reference() | nil) :: :ok | {:error, term()} | ||
| def publish_trace(%Trace{pid: pid} = trace, ref \\ nil) do | ||
| trace | ||
| |> get_event(ref) | ||
| |> Bus.broadcast_trace!(pid) | ||
| rescue | ||
| err -> | ||
| {:error, err} | ||
| end | ||
|
|
||
| defp get_event(%Trace{type: :call} = trace, ref) do | ||
| %TraceCalled{ | ||
| trace_id: trace.id, | ||
| ets_ref: ref, | ||
| module: trace.module, | ||
| function: trace.function, | ||
| pid: trace.pid, | ||
| cid: trace.cid | ||
| } | ||
| end | ||
|
|
||
| defp get_event(%Trace{type: :return_from} = trace, ref) do | ||
| %TraceReturned{ | ||
| trace_id: trace.id, | ||
| ets_ref: ref, | ||
| module: trace.module, | ||
| function: trace.function, | ||
| pid: trace.pid, | ||
| cid: trace.cid | ||
| } | ||
| end | ||
|
|
||
| defp get_event(%Trace{type: :exception_from} = trace, ref) do | ||
| %TraceErrored{ | ||
| trace_id: trace.id, | ||
| ets_ref: ref, | ||
| module: trace.module, | ||
| function: trace.function, | ||
| pid: trace.pid, | ||
| cid: trace.cid | ||
| } | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
190 changes: 190 additions & 0 deletions
190
lib/live_debugger_refactor/services/callback_tracer/gen_servers/trace_handler.ex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| defmodule LiveDebuggerRefactor.Services.CallbackTracer.GenServers.TraceHandler do | ||
| @moduledoc """ | ||
| GenServer for handling trace data. | ||
| """ | ||
|
|
||
| use GenServer | ||
|
|
||
| require Logger | ||
|
|
||
| alias LiveDebuggerRefactor.Utils.Callbacks, as: CallbackUtils | ||
| alias LiveDebuggerRefactor.Services.CallbackTracer.Actions.Trace, as: TraceActions | ||
| alias LiveDebuggerRefactor.Services.CallbackTracer.Actions.Tracing, as: TracingActions | ||
| alias LiveDebuggerRefactor.Structs.Trace | ||
|
|
||
| @allowed_callbacks Enum.map(CallbackUtils.all_callbacks(), &elem(&1, 0)) | ||
|
|
||
| @typedoc """ | ||
| Trace record is a tuple of: | ||
| - reference to ETS table | ||
| - trace struct | ||
| - timestamp of the trace | ||
|
|
||
| We are storing this tuple in the state of this GenServer to calculate execution time of callbacks. | ||
| """ | ||
| @type trace_record :: {reference(), Trace.t(), non_neg_integer()} | ||
kraleppa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @typedoc """ | ||
| Trace key is a tuple of: | ||
| - pid of the process that called the callback | ||
| - module of the callback | ||
| - function of the callback | ||
| """ | ||
| @type trace_key :: {pid(), module(), atom()} | ||
| @type state :: %{trace_key => trace_record} | ||
|
|
||
| @spec start_link(opts :: list()) :: GenServer.on_start() | ||
| def start_link(opts \\ []) do | ||
| GenServer.start_link(__MODULE__, opts, name: __MODULE__) | ||
| end | ||
|
|
||
| @doc """ | ||
| Handles trace from `:dbg.tracer` process. | ||
| """ | ||
| @spec handle_trace(trace :: term(), n :: integer()) :: :ok | ||
| def handle_trace(trace, n) do | ||
| GenServer.cast(__MODULE__, {:new_trace, trace, n}) | ||
| end | ||
|
|
||
| @impl true | ||
| def init(_opts) do | ||
| {:ok, %{}} | ||
| end | ||
|
|
||
| ######################################################### | ||
| # Handling recompile events | ||
| # | ||
| # We catch this trace to know when modules were recompiled. | ||
| # We do not display this trace to user, so we do not have to care about order | ||
| # We need to catch that case because tracer disconnects from modules that were recompiled | ||
| # and we need to reapply tracing patterns to them. | ||
| # This will be replaced in the future with a more efficient way to handle this. | ||
| # https://github.com/software-mansion/live-debugger/issues/592 | ||
| # | ||
| ######################################################### | ||
|
|
||
| @impl true | ||
| def handle_cast( | ||
| {:new_trace, {_, _, :return_from, {Mix.Tasks.Compile.Elixir, _, _}, {:ok, _}, _}, _n}, | ||
| state | ||
| ) do | ||
| Task.start(fn -> | ||
| Process.sleep(100) | ||
| TracingActions.refresh_tracing() | ||
| end) | ||
|
|
||
| {:noreply, state} | ||
| end | ||
|
|
||
| @impl true | ||
| def handle_cast({:new_trace, {_, _, _, {Mix.Tasks.Compile.Elixir, _, _}, _}, _}, state) do | ||
| {:noreply, state} | ||
| end | ||
|
|
||
| @impl true | ||
| def handle_cast({:new_trace, {_, _, _, {Mix.Tasks.Compile.Elixir, _, _}, _, _}, _}, state) do | ||
| {:noreply, state} | ||
| end | ||
|
|
||
| ######################################################### | ||
| # Handling component deletion traces | ||
| # | ||
| # We catch this trace to know when components are deleted. | ||
| # We do not display this trace to user, so we do not have to care about order | ||
| # This will be replaced in the future with telemetry event added in LiveView 1.1.0 | ||
| # https://hexdocs.pm/phoenix_live_view/1.1.0-rc.3/telemetry.html | ||
| # | ||
| ######################################################### | ||
|
|
||
| @impl true | ||
| def handle_cast( | ||
| {:new_trace, | ||
| {_, pid, _, {Phoenix.LiveView.Diff, :delete_component, [cid | _] = args}, ts}, n}, | ||
| state | ||
| ) do | ||
| Task.start(fn -> | ||
| with {:ok, trace} <- TraceActions.create_delete_component_trace(n, args, pid, cid, ts), | ||
| :ok <- TraceActions.publish_trace(trace) do | ||
| :ok | ||
| else | ||
| {:error, err} -> | ||
| raise "Error while handling trace: #{inspect(err)}" | ||
| end | ||
| end) | ||
|
|
||
| {:noreply, state} | ||
| end | ||
|
|
||
| ######################################################### | ||
| # Handling standard callback traces | ||
| # | ||
| # To measure execution time of callbacks we save in GenServer timestamp when callback is called. | ||
| # Since LiveView is a single process all callbacks are called in order. | ||
| # This means that we can measure execution time of callbacks by subtracting timestamp when | ||
| # callback is called from timestamp when callback returns. | ||
| # | ||
| ######################################################### | ||
|
|
||
| @impl true | ||
| def handle_cast({:new_trace, {_, pid, :call, {module, fun, args}, ts}, n}, state) | ||
| when fun in @allowed_callbacks do | ||
| with {:ok, trace} <- TraceActions.create_trace(n, module, fun, args, pid, ts), | ||
| {:ok, ref} <- TraceActions.persist_trace(trace), | ||
| :ok <- TraceActions.publish_trace(trace, ref) do | ||
| {:noreply, put_trace_record(state, trace, ref, ts)} | ||
| else | ||
| {:error, "Transport PID is nil"} -> | ||
| {:noreply, state} | ||
|
|
||
| {:error, err} -> | ||
| raise "Error while handling trace: #{inspect(err)}" | ||
| end | ||
| end | ||
|
|
||
| @impl true | ||
| def handle_cast({:new_trace, {_, pid, type, {module, fun, _}, _, return_ts}, _n}, state) | ||
| when fun in @allowed_callbacks and type in [:return_from, :exception_from] do | ||
| with trace_key <- {pid, module, fun}, | ||
| {ref, trace, ts} <- get_trace_record(state, trace_key), | ||
| execution_time <- calculate_execution_time(return_ts, ts), | ||
| params <- %{execution_time: execution_time, type: type}, | ||
| {:ok, updated_trace} <- TraceActions.update_trace(trace, params), | ||
| {:ok, ref} <- TraceActions.persist_trace(updated_trace, ref), | ||
| :ok <- TraceActions.publish_trace(updated_trace, ref) do | ||
kraleppa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| {:noreply, delete_trace_record(state, trace_key)} | ||
| else | ||
| :trace_record_not_found -> | ||
| {:noreply, state} | ||
|
|
||
| {:error, err} -> | ||
| raise "Error while handling trace: #{inspect(err)}" | ||
| end | ||
| end | ||
|
|
||
| ######################################################### | ||
| # Handling unknown traces | ||
| ######################################################### | ||
|
|
||
| @impl true | ||
| def handle_cast({:new_trace, trace, _n}, state) do | ||
| Logger.info("Ignoring unexpected trace: #{inspect(trace)}") | ||
|
|
||
| {:noreply, state} | ||
| end | ||
|
|
||
| defp put_trace_record(state, trace, ref, timestamp) do | ||
| Map.put(state, {trace.pid, trace.module, trace.function}, {ref, trace, timestamp}) | ||
| end | ||
|
|
||
| defp get_trace_record(state, trace_key) do | ||
| Map.get(state, trace_key, :trace_record_not_found) | ||
| end | ||
|
|
||
| defp delete_trace_record(state, trace_key) do | ||
| Map.delete(state, trace_key) | ||
| end | ||
|
|
||
| defp calculate_execution_time(return_ts, call_ts) do | ||
| :timer.now_diff(return_ts, call_ts) | ||
| end | ||
| end | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.