Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
background: transparent;
}

.remove-arrow::-webkit-inner-spin-button,
.remove-arrow::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.remove-arrow {
-moz-appearance: textfield;
}

body::-webkit-scrollbar {
width: 8px;
height: 8px;
Expand Down
2 changes: 1 addition & 1 deletion dev/live_views/main.ex
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ defmodule LiveDebuggerDev.LiveViews.Main do
end

def handle_event("very-slow-increment", _, socket) do
Process.sleep(2500)
Process.sleep(1100)
{:noreply, update(socket, :counter_very_slow, &(&1 + 1))}
end

Expand Down
14 changes: 14 additions & 0 deletions lib/live_debugger/utils/parsers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ defmodule LiveDebugger.Utils.Parsers do
This module provides functions to parse some structs to string representation and vice versa.
"""

@time_units ["µs", "ms", "s"]

@spec time_units() :: [String.t()]
def time_units(), do: @time_units

@spec parse_timestamp(non_neg_integer()) :: String.t()
def parse_timestamp(timestamp) do
timestamp
Expand All @@ -29,6 +34,15 @@ defmodule LiveDebugger.Utils.Parsers do
end
end

@spec time_to_microseconds(value :: non_neg_integer(), unit :: String.t()) :: non_neg_integer()
def time_to_microseconds(value, unit) when unit in @time_units do
case unit do
"s" -> value * 1_000_000
"ms" -> value * 1_000
"µs" -> value
end
end

@spec pid_to_string(pid :: pid()) :: String.t()
def pid_to_string(pid) when is_pid(pid) do
pid |> :erlang.pid_to_list() |> to_string() |> String.slice(1..-2//1)
Expand Down
59 changes: 25 additions & 34 deletions lib/live_debugger_web/components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ defmodule LiveDebuggerWeb.Components do

use Phoenix.Component

import Phoenix.HTML

alias LiveDebugger.Utils.Parsers
alias Phoenix.LiveView.JS

Expand Down Expand Up @@ -74,46 +72,39 @@ defmodule LiveDebuggerWeb.Components do
"""
end

@doc """
Renders an input with label.
"""
attr(:field, Phoenix.HTML.FormField, required: true)
attr(:label_text, :string, default: nil)
attr(:label_raw, :boolean, default: false)
attr(:type, :string, default: "text")
attr(:value_field, Phoenix.HTML.FormField, required: true)
attr(:unit_field, Phoenix.HTML.FormField, required: true)
attr(:units, :list, required: true)
attr(:rest, :global, include: ~w(min max placeholder))

attr(:wrapper_class, :any, default: nil)
attr(:input_class, :any, default: nil)
attr(:label_class, :any, default: nil)
attr(:rest, :global, include: ~w(min max))

def input(assigns) do
def input_with_units(assigns) do
assigns =
assigns
|> assign(:errors, assigns.field.errors)
|> assign(:errors, assigns.value_field.errors)

~H"""
<div phx-feedback-for={@field.name} class={["" | List.wrap(@wrapper_class)]}>
<label for={@field.id} class={["block font-medium text-xs" | List.wrap(@label_class)]}>
<%= if @label_raw, do: raw(@label_text), else: @label_text %>
</label>
<div class={[
"shadow-sm flex items-center rounded-[4px] outline outline-1 -outline-offset-1 has-[input:focus-within]:outline has-[input:focus-within]:outline-2 has-[input:focus-within]:-outline-offset-2",
@errors == [] && "outline-default-border has-[input:focus-within]:outline-ui-accent",
@errors != [] && "outline-error-text has-[input:focus-within]:outline-error-text"
]}>
<input
type={@type}
name={@field.name}
id={@field.id}
value={Phoenix.HTML.Form.normalize_value(@type, @field.value)}
class={[
"mt-2 block w-full rounded-lg bg-surface-1-bg focus:ring-0 text-xs",
"phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400",
@errors == [] && "border-default-border focus:border-secondary-text",
@errors != [] && "border-error-text focus:border-error-text"
| List.wrap(@input_class)
]}
id={@value_field.id}
name={@value_field.name}
type="number"
class="block remove-arrow max-w-20 bg-surface-0-bg border-none py-2.5 pl-2 pr-3 text-xs text-primary-text placeholder:text-ui-muted focus:ring-0"
value={Phoenix.HTML.Form.normalize_value("number", @value_field.value)}
{@rest}
/>
<p :for={msg <- @errors} class="mt-2 block text-error-text">
<%= msg %>
</p>
<div class="grid shrink-0 grid-cols-1 focus-within:relative">
<select
id={@unit_field.id}
name={@unit_field.name}
class="border-none bg-surface-0-bg col-start-1 row-start-1 w-full appearance-none rounded-md py-1.5 pl-3 pr-7 text-xs text-secondary-text placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ui-accent"
>
<%= Phoenix.HTML.Form.options_for_select(@units, @unit_field.value) %>
</select>
</div>
</div>
"""
end
Expand Down
16 changes: 13 additions & 3 deletions lib/live_debugger_web/live/traces_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,9 @@ defmodule LiveDebuggerWeb.TracesLive do
functions: functions,
execution_time: [
{:exec_time_max, ""},
{:exec_time_min, ""}
{:exec_time_min, ""},
{:min_unit, ""},
{:max_unit, ""}
]
}
end
Expand All @@ -438,9 +440,17 @@ defmodule LiveDebuggerWeb.TracesLive do
end

defp get_execution_times(socket) do
socket.assigns.current_filters.execution_time
|> Enum.filter(fn {_, value} -> value != "" end)
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
Expand Down
50 changes: 32 additions & 18 deletions lib/live_debugger_web/live_components/filters_form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule LiveDebuggerWeb.LiveComponents.FiltersForm do

alias LiveDebugger.Utils.Callbacks, as: UtilsCallbacks
alias LiveDebugger.Structs.TreeNode
alias LiveDebugger.Utils.Parsers

@impl true
def update(assigns, socket) do
Expand All @@ -22,36 +23,41 @@ defmodule LiveDebuggerWeb.LiveComponents.FiltersForm do
@impl true
def render(assigns) do
assigns =
assign(assigns, :selected_filters_number, calculate_selected_filters(assigns.form))
assigns
|> assign(:selected_filters_number, calculate_selected_filters(assigns.form))
|> assign(:errors, assigns.form.errors)

~H"""
<div id={@id <> "-wrapper"}>
<.form for={@form} phx-submit="submit" phx-change="change" phx-target={@myself}>
<div class="w-52">
<div class="w-96">
<div class="p-4">
<p class="font-medium mb-4">Callbacks</p>
<div class="flex flex-col gap-3">
<%= for {function, arity} <- get_callbacks(@node_id) do %>
<.checkbox field={@form[function]} label={"#{function}/#{arity}"} />
<% end %>
</div>
<p class="font-medium mb-4 mt-6">Callback execution time</p>
<div class="flex flex-col gap-3">
<.input
label_text="max [&micro;s]"
label_raw
field={@form[:exec_time_max]}
type="number"
<p class="font-medium mb-4 mt-6">Execution Time</p>
<div class="mt-3 flex gap-3 items-center">
<.input_with_units
value_field={@form[:exec_time_min]}
unit_field={@form[:min_unit]}
units={Parsers.time_units()}
min="0"
/>
<.input
label_text="min [&micro;s]"
label_raw
field={@form[:exec_time_min]}
type="number"
placeholder="min"
/> -
<.input_with_units
value_field={@form[:exec_time_max]}
unit_field={@form[:max_unit]}
min="0"
units={Parsers.time_units()}
placeholder="max"
/>
</div>
<p :for={{_, msg} <- @errors} class="mt-2 block text-error-text">
<%= msg %>
</p>
</div>
<div class="flex py-3 px-4 border-t border-default-border items-center justify-between">
<button
Expand Down Expand Up @@ -151,17 +157,25 @@ defmodule LiveDebuggerWeb.LiveComponents.FiltersForm do
end

defp validate_execution_time(execution_time) do
min_time = Keyword.get(execution_time, :exec_time_min, 0)
max_time = Keyword.get(execution_time, :exec_time_max, :infinity)
min_time = execution_time[:exec_time_min]
max_time = execution_time[:exec_time_max]
min_time_unit = execution_time[:min_unit]
max_time_unit = execution_time[:max_unit]

if min_time != "" and max_time != "" and
String.to_integer(min_time) > String.to_integer(max_time) do
apply_unit_factor(min_time, min_time_unit) > apply_unit_factor(max_time, max_time_unit) do
{:error, [exec_time_min: "min must be less than max", exec_time_max: ""]}
else
:ok
end
end

defp apply_unit_factor(value, unit) do
value
|> String.to_integer()
|> Parsers.time_to_microseconds(unit)
end

defp calculate_selected_filters(form) do
callbacks =
UtilsCallbacks.callbacks_functions()
Expand Down
91 changes: 58 additions & 33 deletions test/live_debugger/channel_dashboard_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ defmodule LiveDebugger.ChannelDashboardTest do
|> assert_has(assigns_entry(key: "counter", value: "0"))
|> assert_has(traces(count: 2))

# Callback traces appear in debugger
dev_app
|> click(button("increment-button"))
|> click(button("increment-button"))
Expand Down Expand Up @@ -71,41 +70,12 @@ defmodule LiveDebugger.ChannelDashboardTest do
dev_app
|> click(button("very-slow-increment-button"))

Process.sleep(2505)
Process.sleep(1105)

debugger
|> find(traces(count: 4))
|> Enum.at(1)
|> assert_has(css("span.text-error-text", text: "2.50 s"))

# Filtering callback traces by execution time works
debugger
|> click(toggle_tracing_button())
|> click(filters_button())
|> fill_in(text_field("exec_time_min"), with: 100)
|> fill_in(text_field("exec_time_max"), with: 2_000_000)
|> send_keys([:enter])
|> find(traces(count: 1))
|> find(css("span.text-warning-text"))
|> Element.text()
|> String.match?(~r"^40\d ms$")

debugger
|> click(toggle_tracing_button())

dev_app
|> click(button("increment-button"))
|> click(button("slow-increment-button"))

Process.sleep(405)

debugger
|> find(traces(count: 2))
|> Enum.each(fn trace ->
find(trace, css("span.text-warning-text"))
|> Element.text()
|> String.match?(~r"^40\d ms$")
end)
|> assert_has(css("span.text-error-text", text: "1.10 s"))
end

@sessions 2
Expand Down Expand Up @@ -159,7 +129,7 @@ defmodule LiveDebugger.ChannelDashboardTest do
end

@sessions 2
feature "user can filter callback traces", %{sessions: [dev_app, debugger]} do
feature "user can filter traces by callback name", %{sessions: [dev_app, debugger]} do
LiveDebugger.GenServers.CallbackTracingServer.ping!()

dev_app
Expand Down Expand Up @@ -243,6 +213,61 @@ defmodule LiveDebugger.ChannelDashboardTest do
])
end

@sessions 2
feature "user can filter traces by execution time", %{sessions: [dev_app, debugger]} do
LiveDebugger.GenServers.CallbackTracingServer.ping!()

dev_app
|> visit(@dev_app_url)
|> click(button("slow-increment-button"))

Process.sleep(405)

dev_app
|> click(button("very-slow-increment-button"))

Process.sleep(1105)

debugger
|> visit("/")
|> click(first_link())
|> assert_traces(6, [
"render/1",
"handle_event/3",
"render/1",
"handle_event/3",
"render/1",
"mount/3"
])
|> click(filters_button())
|> set_value(select("min_unit"), "ms")
|> fill_in(text_field("exec_time_min"), with: 100)
|> set_value(select("max_unit"), "s")
|> fill_in(text_field("exec_time_max"), with: 1)
|> send_keys([:enter])
|> find(traces(count: 1))
|> find(css("span.text-warning-text"))
|> Element.text()
|> String.match?(~r"^40\d ms$")

debugger
|> click(toggle_tracing_button())

dev_app
|> click(button("increment-button"))
|> click(button("slow-increment-button"))

Process.sleep(405)

debugger
|> find(traces(count: 2))
|> Enum.each(fn trace ->
find(trace, css("span.text-warning-text"))
|> Element.text()
|> String.match?(~r"^40\d ms$")
end)
end

defp assert_traces(session, count, callback_names) do
session
|> find(traces(count: count))
Expand Down