Skip to content

Commit 4014e74

Browse files
authored
Task: implement displaying event handling time (#277)
* Get execution time using dbg trace return_from * Implement callback execution time measuring * Display callback execution time * Add spinner * Add parsing elapsed time * Change execution time visualization * Add timer * Small tweaks * Remove console.log * Rename hook * Change timer stopping * Refactor * Add tooltips * Refactor * Fix infinite loading spinner * Show only finished callbacks on refresh * Fix trace callback return after tracing stopped * Save timestamp to trace from tracer handler * Update TraceService test * fix * Update tests * Add e2e test cases
1 parent c0d3e7e commit 4014e74

21 files changed

Lines changed: 377 additions & 106 deletions

File tree

assets/css/themes/dark.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,8 @@
6161
--error-border: var(--gray-700);
6262
--error-icon: var(--swm-pink-80);
6363
--error-text: var(--swm-pink-80);
64+
65+
/* Warning */
66+
--warning-text: var(--swm-yellow-40);
6467
}
6568
}

assets/css/themes/light.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,8 @@
6161
--error-border: var(--red-100);
6262
--error-icon: var(--red-500);
6363
--error-text: var(--red-800);
64+
65+
/* Warning */
66+
--warning-text: #9a7e00;
6467
}
6568
}

assets/js/hooks.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Tooltip from './hooks/tooltip';
88
import Highlight from './hooks/highlight';
99
import LiveDropdown from './hooks/live_dropdown';
1010
import AutoClearFlash from './hooks/auto_clear_flash';
11+
import TraceExecutionTime from './hooks/trace_execution_time';
1112

1213
import topbar from '../vendor/topbar';
1314

@@ -28,6 +29,7 @@ function createHooks() {
2829
Highlight,
2930
LiveDropdown,
3031
AutoClearFlash,
32+
TraceExecutionTime,
3133
};
3234
}
3335

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const TraceExecutionTime = {
2+
mounted() {
3+
let start = Date.now();
4+
let current = start;
5+
let handled = false;
6+
7+
this.intervalId = setInterval(() => {
8+
current = Date.now() - start;
9+
this.el.textContent = current + ' ms';
10+
}, 16);
11+
12+
this.handleEvent('stop-timer', () => {
13+
if (!handled) {
14+
clearInterval(this.intervalId);
15+
this.el.closest('details').open = false;
16+
handled = true;
17+
}
18+
});
19+
},
20+
destroyed() {
21+
clearInterval(this.intervalId);
22+
},
23+
};
24+
25+
export default TraceExecutionTime;

assets/tailwind.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ module.exports = {
6262
'error-border': 'var(--error-border)',
6363
'error-icon': 'var(--error-icon)',
6464
'error-text': 'var(--error-text)',
65+
'warning-text': 'var(--warning-text)',
6566
},
6667
screens: { xs: '380px' },
6768
fontFamily: {

dev/live_views/main.ex

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ defmodule LiveDebuggerDev.LiveViews.Main do
77
socket =
88
socket
99
|> assign(counter: 0)
10+
|> assign(counter_slow: 0)
11+
|> assign(counter_very_slow: 0)
1012
|> assign(datetime: nil)
1113
|> assign(name: random_name())
1214
|> assign(single_element_list: [%Phoenix.LiveComponent.CID{cid: 1}])
@@ -29,6 +31,18 @@ defmodule LiveDebuggerDev.LiveViews.Main do
2931
</.button>
3032
<span class="text-xl"><%= @counter %></span>
3133
</div>
34+
<div class="flex items-center gap-2">
35+
<.button id="slow-increment-button" phx-click="slow-increment" color="blue">
36+
Slow Increment
37+
</.button>
38+
<span class="text-xl"><%= @counter_slow %></span>
39+
</div>
40+
<div class="flex items-center gap-2">
41+
<.button id="very-slow-increment-button" phx-click="very-slow-increment" color="blue">
42+
Very Slow Increment
43+
</.button>
44+
<span class="text-xl"><%= @counter_very_slow %></span>
45+
</div>
3246
<div class="flex items-center gap-1">
3347
<.button id="update-button" phx-click="change_name" color="red">
3448
Update
@@ -72,7 +86,17 @@ defmodule LiveDebuggerDev.LiveViews.Main do
7286
end
7387

7488
def handle_event("increment", _, socket) do
75-
{:noreply, assign(socket, :counter, socket.assigns.counter + 1)}
89+
{:noreply, update(socket, :counter, &(&1 + 1))}
90+
end
91+
92+
def handle_event("slow-increment", _, socket) do
93+
Process.sleep(400)
94+
{:noreply, update(socket, :counter_slow, &(&1 + 1))}
95+
end
96+
97+
def handle_event("very-slow-increment", _, socket) do
98+
Process.sleep(2500)
99+
{:noreply, update(socket, :counter_very_slow, &(&1 + 1))}
76100
end
77101

78102
def handle_event("change_name", _, socket) do

lib/live_debugger/gen_servers/callback_tracing_server.ex

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do
4545
@impl true
4646
def handle_info(:setup_tracing, state) do
4747
Dbg.tracer(:process, {&handle_trace/2, 0})
48-
Dbg.p(:all, :c)
48+
Dbg.p(:all, [:c, :timestamp])
4949

5050
all_modules = ModuleDiscoveryService.all_modules()
5151

@@ -58,7 +58,10 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do
5858
|> ModuleDiscoveryService.live_component_modules()
5959
|> CallbackUtils.live_component_callbacks()
6060
|> Enum.concat(callbacks)
61-
|> Enum.each(fn mfa -> Dbg.tp(mfa, []) end)
61+
|> Enum.each(fn mfa ->
62+
Dbg.tp(mfa, [{:_, [], [{:return_trace}]}])
63+
Dbg.tp(mfa, [{:_, [], [{:exception_trace}]}])
64+
end)
6265

6366
# This is not a callback created by user
6467
# We trace it to refresh the components tree
@@ -76,7 +79,10 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do
7679
# Because of that we do it asynchronously to speed up tracer a bit
7780
# We do not persist this trace because it is not displayed to user
7881
@spec handle_trace(term(), n :: integer()) :: integer()
79-
defp handle_trace({_, pid, _, {Phoenix.LiveView.Diff, :delete_component, [cid | _] = args}}, n) do
82+
defp handle_trace(
83+
{_, pid, _, {Phoenix.LiveView.Diff, :delete_component, [cid | _] = args}, timestamp},
84+
n
85+
) do
8086
Task.start(fn ->
8187
with cid <- %Phoenix.LiveComponent.CID{cid: cid},
8288
{:ok, %{socket: socket}} <- ChannelService.state(pid),
@@ -89,6 +95,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do
8995
:delete_component,
9096
args,
9197
pid,
98+
timestamp,
9299
socket_id: socket_id,
93100
transport_pid: transport_pid,
94101
cid: cid
@@ -102,16 +109,36 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do
102109

103110
# This handles callbacks created by user that will be displayed to user
104111
# It cannot be async because we care about order
105-
defp handle_trace({_, pid, _, {module, fun, args}}, n) when fun in @callback_functions do
106-
with trace <- Trace.new(n, module, fun, args, pid),
112+
defp handle_trace({_, pid, :call, {module, fun, args}, timestamp}, n)
113+
when fun in @callback_functions do
114+
with trace <- Trace.new(n, module, fun, args, pid, timestamp),
107115
true <- is_pid(trace.transport_pid),
108116
:ok <- persist_trace(trace) do
117+
:erlang.put({pid, module, fun}, {timestamp, trace})
109118
publish_trace(trace)
110119
end
111120

112121
n - 1
113122
end
114123

124+
defp handle_trace({_, pid, :return_from, {module, fun, _arity}, _, return_ts}, n)
125+
when fun in @callback_functions do
126+
with {call_ts, trace} <- :erlang.get({pid, module, fun}),
127+
execution_time <- :timer.now_diff(return_ts, call_ts),
128+
trace <- %{trace | execution_time: execution_time},
129+
:ok <- persist_trace(trace) do
130+
:erlang.erase({pid, module, fun})
131+
publish_update_trace(trace)
132+
end
133+
134+
n
135+
end
136+
137+
defp handle_trace({_, _pid, :exception_from, {_module, fun, _}, _, _timestamp}, n)
138+
when fun in @callback_functions do
139+
n
140+
end
141+
115142
defp handle_trace(trace, n) do
116143
Logger.info("Ignoring unexpected trace: #{inspect(trace)}")
117144
n
@@ -138,6 +165,16 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do
138165
{:error, err}
139166
end
140167

168+
@spec publish_update_trace(Trace.t()) :: :ok | {:error, term()}
169+
defp publish_update_trace(%Trace{} = trace) do
170+
do_publish_update(trace)
171+
:ok
172+
rescue
173+
err ->
174+
Logger.error("Error while publishing trace: #{inspect(err)}")
175+
{:error, err}
176+
end
177+
141178
@spec do_publish(Trace.t()) :: :ok
142179
defp do_publish(%{module: Phoenix.LiveView.Diff} = trace) do
143180
trace
@@ -152,11 +189,23 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do
152189
fun = trace.function
153190

154191
socket_id
155-
|> PubSubUtils.tsnf_topic(transport_pid, node_id, fun)
192+
|> PubSubUtils.tsnf_topic(transport_pid, node_id, fun, :call)
156193
|> PubSubUtils.broadcast({:new_trace, trace})
157194

158195
socket_id
159196
|> PubSubUtils.ts_f_topic(transport_pid, fun)
160197
|> PubSubUtils.broadcast({:new_trace, trace})
161198
end
199+
200+
@spec do_publish_update(Trace.t()) :: :ok
201+
defp do_publish_update(trace) do
202+
socket_id = trace.socket_id
203+
node_id = Trace.node_id(trace)
204+
transport_pid = trace.transport_pid
205+
fun = trace.function
206+
207+
socket_id
208+
|> PubSubUtils.tsnf_topic(transport_pid, node_id, fun, :return)
209+
|> PubSubUtils.broadcast({:updated_trace, trace})
210+
end
162211
end

lib/live_debugger/services/trace_service.ex

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,22 @@ defmodule LiveDebugger.Services.TraceService do
120120

121121
defp match_spec(node_id, functions) when is_pid(node_id) do
122122
[
123-
{{:_, %{function: :"$1", pid: node_id, cid: nil}}, to_spec(functions), [:"$_"]}
123+
{{:_, %{function: :"$1", execution_time: :"$2", pid: node_id, cid: nil}},
124+
to_spec(functions), [:"$_"]}
124125
]
125126
end
126127

127128
defp match_spec(%CID{} = node_id, functions) do
128-
[{{:_, %{function: :"$1", cid: node_id}}, to_spec(functions), [:"$_"]}]
129+
[{{:_, %{function: :"$1", execution_time: :"$2", cid: node_id}}, to_spec(functions), [:"$_"]}]
129130
end
130131

131132
defp match_spec(nil, functions) do
132-
[{{:_, %{function: :"$1"}}, to_spec(functions), [:"$_"]}]
133+
[{{:_, %{function: :"$1", execution_time: :"$2"}}, to_spec(functions), [:"$_"]}]
133134
end
134135

135-
def to_spec([]), do: []
136+
def to_spec([]), do: [{:"/=", :"$2", nil}]
136137

137-
def to_spec([single]), do: [{:"=:=", :"$1", single}]
138+
def to_spec([single]), do: [{:andalso, {:"=:=", :"$1", single}, {:"/=", :"$2", nil}}]
138139

139140
def to_spec([first, second | rest]) do
140141
initial_orelse = {:orelse, List.first(to_spec([first])), List.first(to_spec([second]))}
@@ -144,7 +145,7 @@ defmodule LiveDebugger.Services.TraceService do
144145
{:orelse, acc, List.first(to_spec([x]))}
145146
end)
146147

147-
[result]
148+
[{:andalso, result, {:"/=", :"$2", nil}}]
148149
end
149150

150151
@spec ets_table!(pid :: ets_table_id()) :: :ets.table()

lib/live_debugger/structs/trace.ex

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ defmodule LiveDebugger.Structs.Trace do
2323
:transport_pid,
2424
:pid,
2525
:cid,
26-
:timestamp
26+
:timestamp,
27+
:execution_time
2728
]
2829

30+
@type timestamp() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}
31+
2932
@type t() :: %__MODULE__{
3033
id: integer(),
3134
module: atom(),
@@ -36,14 +39,15 @@ defmodule LiveDebugger.Structs.Trace do
3639
transport_pid: pid() | nil,
3740
pid: pid(),
3841
cid: struct() | nil,
39-
timestamp: non_neg_integer()
42+
timestamp: timestamp(),
43+
execution_time: non_neg_integer() | nil
4044
}
4145

4246
@doc """
4347
Creates a new trace struct.
4448
"""
45-
@spec new(integer(), atom(), atom(), list(), pid(), Keyword.t()) :: t()
46-
def new(id, module, function, args, pid, opts \\ []) do
49+
@spec new(integer(), atom(), atom(), list(), pid(), timestamp(), Keyword.t()) :: t()
50+
def new(id, module, function, args, pid, timestamp, opts \\ []) do
4751
socket_id = Keyword.get(opts, :socket_id, get_socket_id_from_args(args))
4852
transport_pid = Keyword.get(opts, :transport_pid, get_transport_pid_from_args(args))
4953
cid = Keyword.get(opts, :cid, get_cid_from_args(args))
@@ -58,7 +62,8 @@ defmodule LiveDebugger.Structs.Trace do
5862
transport_pid: transport_pid,
5963
pid: pid,
6064
cid: cid,
61-
timestamp: :os.system_time(:microsecond)
65+
timestamp: :timer.now_diff(timestamp, {0, 0, 0}),
66+
execution_time: nil
6267
}
6368
end
6469

lib/live_debugger/structs/trace_display.ex

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@ defmodule LiveDebugger.Structs.TraceDisplay do
66

77
alias LiveDebugger.Structs.Trace
88

9-
defstruct [:id, :trace, :render_body?, :counter]
9+
defstruct [:id, :trace, :render_body?, :from_tracing?, :counter]
1010

1111
@type t() :: %__MODULE__{
1212
id: integer(),
1313
trace: Trace.t(),
14-
render_body?: boolean()
14+
render_body?: boolean(),
15+
from_tracing?: boolean()
1516
}
1617

17-
def from_trace(%Trace{} = trace) do
18-
%__MODULE__{id: trace.id, trace: trace, render_body?: false}
18+
def from_trace(%Trace{} = trace, from_tracing? \\ false) do
19+
%__MODULE__{id: trace.id, trace: trace, render_body?: false, from_tracing?: from_tracing?}
1920
end
2021

2122
def render_body(%__MODULE__{} = trace) do

0 commit comments

Comments
 (0)