Skip to content

Commit 6eef997

Browse files
authored
Task: Implement caching mechanism (#364)
* add crashing component to dev * add state_server * update pub_sub * add tests
1 parent ee0d9d9 commit 6eef997

26 files changed

Lines changed: 558 additions & 295 deletions

dev/live_components/crash.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
defmodule LiveDebuggerDev.LiveComponents.Crash do
2+
use DevWeb, :live_component
3+
4+
def render(assigns) do
5+
~H"""
6+
<div>
7+
<.box title="Crash [LiveComponent]" color="red">
8+
<.button phx-click="crash" color="red" phx-target={@myself}>Crash</.button>
9+
</.box>
10+
</div>
11+
"""
12+
end
13+
end

dev/live_views/main.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ defmodule LiveDebuggerDev.LiveViews.Main do
5858
5959
<.live_component id="many_assigns" module={LiveComponents.ManyAssigns} />
6060
<.live_component id="name_outer" name={@name} module={LiveComponents.Name} />
61+
<.live_component id="crash" module={LiveComponents.Crash} />
6162
<.live_component id="send_outer" module={LiveComponents.Send}>
6263
<.live_component id="name_inner" name={@name} module={LiveComponents.Name} />
6364
<.live_component id="long_name" module={LiveComponents.LiveComponentWithVeryVeryLongName} />

lib/live_debugger.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ defmodule LiveDebugger do
3939
else
4040
children ++
4141
[
42+
{LiveDebugger.GenServers.StateServer, []},
4243
{LiveDebugger.GenServers.CallbackTracingServer, []},
4344
{LiveDebugger.GenServers.EtsTableServer, []}
4445
]

lib/live_debugger/gen_servers/callback_tracing_server.ex

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -177,23 +177,18 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do
177177

178178
@spec do_publish(Trace.t()) :: :ok
179179
defp do_publish(%{module: Phoenix.LiveView.Diff} = trace) do
180-
trace
181-
|> PubSubUtils.component_deleted_topic()
182-
|> PubSubUtils.broadcast({:new_trace, trace})
180+
PubSubUtils.component_deleted_topic()
181+
|> PubSubUtils.broadcast({:component_deleted, trace})
183182
end
184183

185-
defp do_publish(trace) do
184+
defp do_publish(%Trace{} = trace) do
186185
socket_id = trace.socket_id
187186
node_id = Trace.node_id(trace)
188187
transport_pid = trace.transport_pid
189188
fun = trace.function
190189

191190
socket_id
192-
|> PubSubUtils.tsnf_topic(transport_pid, node_id, fun, :call)
193-
|> PubSubUtils.broadcast({:new_trace, trace})
194-
195-
socket_id
196-
|> PubSubUtils.ts_f_topic(transport_pid, fun)
191+
|> PubSubUtils.trace_topic(transport_pid, node_id, fun, :call)
197192
|> PubSubUtils.broadcast({:new_trace, trace})
198193
end
199194

@@ -204,8 +199,13 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do
204199
transport_pid = trace.transport_pid
205200
fun = trace.function
206201

202+
if fun == :render do
203+
PubSubUtils.node_rendered_topic()
204+
|> PubSubUtils.broadcast({:render_trace, trace})
205+
end
206+
207207
socket_id
208-
|> PubSubUtils.tsnf_topic(transport_pid, node_id, fun, :return)
208+
|> PubSubUtils.trace_topic(transport_pid, node_id, fun, :return)
209209
|> PubSubUtils.broadcast({:updated_trace, trace})
210210
end
211211
end

lib/live_debugger/gen_servers/ets_table_server.ex

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,37 +21,13 @@ defmodule LiveDebugger.GenServers.EtsTableServer do
2121
It creates table if none is associated with given pid
2222
"""
2323
@spec table!(pid :: pid()) :: :ets.table()
24-
def table!(pid) when is_pid(pid) do
25-
impl().table!(pid)
26-
end
24+
def table!(pid) when is_pid(pid), do: impl().table!(pid)
2725

2826
@doc """
2927
If table for given `pid` exists it deletes it from ETS.
3028
"""
3129
@spec delete_table!(pid :: pid()) :: :ok
32-
def delete_table!(pid) when is_pid(pid) do
33-
impl().delete_table!(pid)
34-
end
35-
36-
def impl() do
37-
Application.get_env(:live_debugger, :ets_table_server, __MODULE__.Impl)
38-
end
39-
40-
defmodule Impl do
41-
@moduledoc false
42-
@behaviour LiveDebugger.GenServers.EtsTableServer
43-
@server_module LiveDebugger.GenServers.EtsTableServer
44-
45-
@impl true
46-
def table!(pid) do
47-
GenServer.call(@server_module, {:get_or_create_table, pid}, 1000)
48-
end
49-
50-
@impl true
51-
def delete_table!(pid) do
52-
GenServer.call(@server_module, {:delete_table, pid}, 1000)
53-
end
54-
end
30+
def delete_table!(pid) when is_pid(pid), do: impl().delete_table!(pid)
5531

5632
## GenServer
5733

@@ -69,9 +45,8 @@ defmodule LiveDebugger.GenServers.EtsTableServer do
6945
def handle_info({:DOWN, _, :process, closed_pid, _}, table_refs) do
7046
{_, table_refs} = delete_ets_table(closed_pid, table_refs)
7147

72-
closed_pid
73-
|> PubSubUtils.process_status_topic()
74-
|> PubSubUtils.broadcast({:process_status, :dead})
48+
PubSubUtils.process_status_topic()
49+
|> PubSubUtils.broadcast({:process_status, {:dead, closed_pid}})
7550

7651
{:noreply, table_refs}
7752
end
@@ -95,6 +70,26 @@ defmodule LiveDebugger.GenServers.EtsTableServer do
9570
{:reply, :ok, table_refs}
9671
end
9772

73+
defp impl() do
74+
Application.get_env(:live_debugger, :ets_table_server, __MODULE__.Impl)
75+
end
76+
77+
defmodule Impl do
78+
@moduledoc false
79+
@behaviour LiveDebugger.GenServers.EtsTableServer
80+
@server_module LiveDebugger.GenServers.EtsTableServer
81+
82+
@impl true
83+
def table!(pid) do
84+
GenServer.call(@server_module, {:get_or_create_table, pid}, 1000)
85+
end
86+
87+
@impl true
88+
def delete_table!(pid) do
89+
GenServer.call(@server_module, {:delete_table, pid}, 1000)
90+
end
91+
end
92+
9893
@spec create_ets_table() :: :ets.table()
9994
defp create_ets_table() do
10095
:ets.new(@ets_table_name, [:ordered_set, :public])
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
defmodule LiveDebugger.GenServers.StateServer do
2+
@moduledoc """
3+
This gen_server is responsible for storing the state of the application.
4+
It collects state when `render` or `delete_component` callbacks are traced.
5+
It uses named ETS table to store the state of the LiveView channel process.
6+
When process dies, it removes the state from the table.
7+
"""
8+
9+
use GenServer
10+
11+
alias LiveDebugger.Services.System.ProcessService
12+
alias LiveDebugger.Utils.PubSub, as: PubSubUtils
13+
alias LiveDebugger.CommonTypes
14+
alias LiveDebugger.Structs.Trace
15+
16+
@ets_table_name :lvdbg_states
17+
18+
@callback get(pid :: pid()) :: {:ok, CommonTypes.channel_state()} | {:error, term()}
19+
20+
@doc """
21+
Returns previously stored state of the LiveView channel process identified by `pid`.
22+
If the state is not found, it returns `{:error, :not_found}`.
23+
"""
24+
@spec get(pid :: pid()) :: {:ok, CommonTypes.channel_state()} | {:error, term()}
25+
def get(pid) when is_pid(pid) do
26+
impl().get(pid)
27+
end
28+
29+
@doc false
30+
@spec ets_table_name() :: atom()
31+
def ets_table_name(), do: @ets_table_name
32+
33+
@doc false
34+
def record_id(pid), do: "#{inspect(pid)}"
35+
36+
@doc false
37+
def start_link(args \\ []) do
38+
GenServer.start_link(__MODULE__, args, name: __MODULE__)
39+
end
40+
41+
@impl true
42+
def init(_args) do
43+
:ets.new(@ets_table_name, [:named_table, :public, :ordered_set])
44+
45+
PubSubUtils.node_rendered_topic()
46+
|> PubSubUtils.subscribe!()
47+
48+
PubSubUtils.component_deleted_topic()
49+
|> PubSubUtils.subscribe!()
50+
51+
PubSubUtils.process_status_topic()
52+
|> PubSubUtils.subscribe!()
53+
54+
{:ok, []}
55+
end
56+
57+
@impl true
58+
def handle_info({:component_deleted, trace}, state) do
59+
save_state(trace)
60+
61+
{:noreply, state}
62+
end
63+
64+
def handle_info({:render_trace, trace}, state) do
65+
save_state(trace)
66+
67+
{:noreply, state}
68+
end
69+
70+
def handle_info({:process_status, {:dead, pid}}, state) do
71+
:ets.delete(@ets_table_name, record_id(pid))
72+
73+
{:noreply, state}
74+
end
75+
76+
defp save_state(%Trace{pid: pid} = trace) do
77+
with {:ok, channel_state} <- ProcessService.state(pid) do
78+
record_id = record_id(pid)
79+
:ets.insert(@ets_table_name, {record_id, channel_state})
80+
81+
publish_state_changed(trace, channel_state)
82+
end
83+
end
84+
85+
defp publish_state_changed(%Trace{} = trace, channel_state) do
86+
socket_id = trace.socket_id
87+
transport_pid = trace.transport_pid
88+
node_id = trace.cid || trace.pid
89+
90+
PubSubUtils.state_changed_topic(socket_id, transport_pid, node_id)
91+
|> PubSubUtils.broadcast({:state_changed, channel_state, trace})
92+
93+
PubSubUtils.state_changed_topic(socket_id, transport_pid)
94+
|> PubSubUtils.broadcast({:state_changed, channel_state, trace})
95+
end
96+
97+
defp impl() do
98+
Application.get_env(:live_debugger, :state_server, __MODULE__.Impl)
99+
end
100+
101+
defmodule Impl do
102+
@moduledoc false
103+
104+
@behaviour LiveDebugger.GenServers.StateServer
105+
@server_module LiveDebugger.GenServers.StateServer
106+
107+
def get(pid) do
108+
case :ets.lookup(@server_module.ets_table_name(), @server_module.record_id(pid)) do
109+
[{_, channel_state}] -> {:ok, channel_state}
110+
[] -> {:error, :not_found}
111+
end
112+
end
113+
end
114+
end

lib/live_debugger/services/channel_service.ex

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,15 @@ defmodule LiveDebugger.Services.ChannelService do
44
"""
55

66
alias LiveDebugger.Structs.TreeNode
7-
alias LiveDebugger.Services.System.ProcessService
87
alias LiveDebugger.CommonTypes
8+
alias LiveDebugger.GenServers.StateServer
99

1010
@doc """
1111
Retrieves the state of the LiveView channel process identified by `pid`.
1212
"""
1313
@spec state(pid :: pid()) :: {:ok, CommonTypes.channel_state()} | {:error, term()}
1414
def state(pid) do
15-
case ProcessService.state(pid) do
16-
{:ok, %{socket: %Phoenix.LiveView.Socket{}, components: _} = state} -> {:ok, state}
17-
{:ok, _} -> {:error, "PID: #{inspect(pid)} is not a LiveView process"}
18-
{:error, :not_alive} -> {:error, :not_alive}
19-
{:error, _} -> {:error, "Could not get state from pid: #{inspect(pid)}"}
20-
end
15+
StateServer.get(pid)
2116
end
2217

2318
@doc """

lib/live_debugger/services/system/dbg_service.ex

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,22 @@ defmodule LiveDebugger.Services.System.DbgService do
2929
@spec tracer(:process, handler_spec()) :: {:ok, pid()} | {:error, term()}
3030
@spec tracer(:module, module_spec()) :: {:ok, pid()} | {:error, term()}
3131
@spec tracer(:file, filename :: :file.name_all()) :: {:ok, pid()} | {:error, term()}
32-
def tracer(type, handler_spec) do
33-
impl().tracer(type, handler_spec)
34-
end
32+
def tracer(type, handler_spec), do: impl().tracer(type, handler_spec)
3533

3634
@doc """
3735
Wrapper for `:dbg.p/2`.
3836
Traces `Item` in accordance to the value specified by `Flags`.
3937
`p` stands for **p**rocess.
4038
"""
4139
@spec p(item :: term(), flags :: term()) :: {:ok, match_desc()} | {:error, term()}
42-
def p(item, flags) do
43-
impl().p(item, flags)
44-
end
40+
def p(item, flags), do: impl().p(item, flags)
4541

4642
@doc """
4743
Wrapper for `:dbg.tp/2` that sets up a trace pattern.
4844
Enables call trace for one or more exported functions specified by `ModuleOrMFA`.
4945
"""
5046
@spec tp(module() | mfa(), match_spec :: term()) :: {:ok, match_desc()} | {:error, term()}
51-
def tp(module, match_spec) do
52-
impl().tp(module, match_spec)
53-
end
47+
def tp(module, match_spec), do: impl().tp(module, match_spec)
5448

5549
defp impl() do
5650
Application.get_env(

lib/live_debugger/structs/trace.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,22 @@ defmodule LiveDebugger.Structs.Trace do
3131

3232
@type t() :: %__MODULE__{
3333
id: integer(),
34-
module: atom(),
34+
module: module(),
3535
function: atom(),
3636
arity: non_neg_integer(),
3737
args: list(),
3838
socket_id: String.t(),
3939
transport_pid: pid() | nil,
4040
pid: pid(),
4141
cid: struct() | nil,
42-
timestamp: timestamp(),
42+
timestamp: integer(),
4343
execution_time: non_neg_integer() | nil
4444
}
4545

4646
@doc """
4747
Creates a new trace struct.
4848
"""
49-
@spec new(integer(), atom(), atom(), list(), pid(), timestamp(), Keyword.t()) :: t()
49+
@spec new(integer(), module(), atom(), list(), pid(), timestamp(), Keyword.t()) :: t()
5050
def new(id, module, function, args, pid, timestamp, opts \\ []) do
5151
socket_id = Keyword.get(opts, :socket_id, get_socket_id_from_args(args))
5252
transport_pid = Keyword.get(opts, :transport_pid, get_transport_pid_from_args(args))

0 commit comments

Comments
 (0)