Skip to content
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

use PhoenixProfiler on the Endpoint #58

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9f1fe16
Simplify tests
mcrumm Feb 25, 2022
7b7a567
wip: profile
mcrumm Feb 26, 2022
b9c77f0
maybe mount profile
mcrumm Feb 27, 2022
29d0790
Consolidate profiler modules
mcrumm Feb 27, 2022
d9fccc6
WIP: Add profiler Endpoint
mcrumm Jan 14, 2022
4cd967a
profile thru endpoint
mcrumm Feb 27, 2022
aaddcc6
Clarify endpoint config
mcrumm Feb 27, 2022
d2dd397
docs
mcrumm Feb 27, 2022
fb43de4
docs
mcrumm Feb 27, 2022
eac1914
remove unused key
mcrumm Feb 27, 2022
b0ff21d
use PhoenixProfiler
mcrumm Feb 27, 2022
a595d16
docs
mcrumm Feb 27, 2022
321c0c1
Fix dashboard
mcrumm Feb 27, 2022
a1197b3
Apply limit to dashboard profile list
mcrumm Feb 27, 2022
d3dcadd
dashboard profile time
mcrumm Feb 27, 2022
26ec171
ignore live_reload
mcrumm Feb 27, 2022
6889820
endpoint tests
mcrumm Mar 1, 2022
155a102
Collect exception from endpoint
mcrumm Mar 1, 2022
640c318
Rename plug telemetry
mcrumm Mar 2, 2022
00e6b2f
Sync (dis|en)abling telemetry collector
mcrumm Mar 3, 2022
670c55b
Stabilize profiler responses
mcrumm Mar 3, 2022
d83bc07
Update collector controls on ToolbarLive
mcrumm Mar 3, 2022
a54ffb5
Fix empty toolbar render
mcrumm Mar 3, 2022
8b2efeb
Merge branch 'main' into mc-endpoint
mcrumm Mar 3, 2022
05b3ce5
Add endpoint notes to changelog
mcrumm Mar 3, 2022
c8d3bd9
Add dashboard limit to changelog
mcrumm Mar 3, 2022
ae6ca03
Drop unused helper
mcrumm Mar 3, 2022
f4917af
Validate exceptions on tokens thru the Endpoint
mcrumm Mar 4, 2022
c2c404a
Endpoint integration tests wait once for expected result
mcrumm Mar 4, 2022
d08a88d
Send real result after retry
mcrumm Mar 4, 2022
dd8d968
increase wait before retrying
mcrumm Mar 4, 2022
972bbc0
Simplify waiting on profile data for endpoint test
mcrumm Mar 4, 2022
f87dfd9
increase wait timeout
mcrumm Mar 4, 2022
00cc9bd
Slight wait between checks
mcrumm Mar 4, 2022
1720cde
Add [:phoenix, :error_rendered] to telemetry events
mcrumm Mar 4, 2022
ee8e839
debug test output
mcrumm Mar 11, 2022
c869827
drop debug statements
mcrumm Mar 11, 2022
25514df
normalize exception tests
mcrumm Mar 11, 2022
e3e9b91
Merge branch 'main' into mc-endpoint
mcrumm Jan 21, 2023
a9e0eaf
assert capture_log
mcrumm Jan 21, 2023
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
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

* Ensure dashboard profiles list respects the selected limit

### Changed

#### `use PhoenixProfiler` on your Endpoint

PhoenixProfiler needs to wrap the whole Plug pipeline to get
a complete picture of each request. Make the following changes
in your Endpoint module(s):

1. Add `use PhoenixProfiler` directly after `use Phoenix.Endpoint`:

```diff
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
+ use PhoenixProfiler
```

2. Remove the plug from the `code_reloading?` block:

```diff
if code_reloading? do
- plug PhoenixProfiler
end
```

## [0.2.0] - 2022-09-28

### Added
Expand Down
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ Provides a **development tool** that gives detailed information about the execut
To start using the profiler, you will need the following steps:

1. Add the `phoenix_profiler` dependency
2. Define a profiler on your supervision tree
3. Enable the profiler on your Endpoint
2. Define a profiler server on your supervision tree
3. Enable your profiler on your Endpoint config
4. Configure LiveView
5. Add the `PhoenixProfiler` plug
5. Use `PhoenixProfiler` on your Endpoint module
6. Mount the profiler on your LiveViews
7. Add the profiler page on your LiveDashboard (optional)

Expand All @@ -39,10 +39,10 @@ Add phoenix_profiler to your `mix.exs`:
{:phoenix_profiler, "~> 0.2.0"}
```

### 2. Define a profiler on your supervision tree
### 2. Define a profiler server on your supervision tree

You define a profiler on your main application's telemetry supervision
tree (usually in `lib/my_app_web/telemetry.ex`):
You define a profiler on your telemetry supervision tree
(usually in `lib/my_app_web/telemetry.ex`):

```elixir
children = [
Expand All @@ -61,7 +61,7 @@ The following options are available:
* `:request_sweep_interval` - How often to sweep the ETS table where
the profiles are stored. Default is `24h` in milliseconds.

### 3. Enable the profiler on your Endpoint
### 3. Enable your profiler on your Endpoint config

PhoenixProfiler is disabled by default. In order to enable it,
you must update your endpoint's `:dev` configuration to include the
Expand Down Expand Up @@ -104,24 +104,26 @@ config :my_app, MyAppWeb.Endpoint,
live_view: [signing_salt: "SECRET_SALT"]
```

### 5. Add the PhoenixProfiler plug
### 5. Use PhoenixProfiler

Add the `PhoenixProfiler` plug within the `code_reloading?`
block on your Endpoint (usually in `lib/my_app_web/endpoint.ex`):
Add `use PhoenixProfiler` on your Endpoint module
(usually in `lib/my_app_web/endpoint.ex`):

```elixir
if code_reloading? do
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
use PhoenixProfiler

# plugs...
plug PhoenixProfiler
end
```

### 6. Mount the profiler on your LiveViews
### 6. Mount PhoenixProfiler on your LiveViews

Note this section is required only if you are using LiveView, otherwise you may skip it.

Add the profiler hook to the `live_view` function on your
web module (usually in `lib/my_app_web.ex`):
Add PhoenixProfiler to the `live_view` function on your web
module (usually in `lib/my_app_web.ex`):

```elixir
def live_view do
Expand Down
11 changes: 5 additions & 6 deletions dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ defmodule DemoWeb.AppLive.Index do

defp apply_profiler_toggle(socket) do
if connected?(socket) do
profile = socket.private.phoenix_profiler
next = if profile.info == :enable, do: :disable, else: :enable
info = socket.private.phoenix_profiler.info
next = if info == :enable, do: :disable, else: :enable
assign(socket, :toggle_text, String.capitalize(to_string(next)) <> " Profiler")
else
assign(socket, :toggle_text, nil)
Expand Down Expand Up @@ -269,8 +269,8 @@ defmodule DemoWeb.AppLive.Index do
end

def handle_event("toggle-profiler", _, socket) do
profile = socket.private.phoenix_profiler
next = if profile.info == :enable, do: :disable, else: :enable
info = socket.private.phoenix_profiler.info
next = if info == :enable, do: :disable, else: :enable
socket = apply(PhoenixProfiler, next, [socket])

{:noreply, apply_profiler_toggle(socket)}
Expand Down Expand Up @@ -335,8 +335,7 @@ end

defmodule DemoWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :phoenix_profiler

plug PhoenixProfiler
use PhoenixProfiler

@session_options [
store: :cookie,
Expand Down
71 changes: 34 additions & 37 deletions lib/phoenix_profiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,32 @@ defmodule PhoenixProfiler do
|> String.split("<!-- MDOC -->")
|> Enum.fetch!(1)

defmacro __using__(_) do
quote do
unquote(plug())

@before_compile PhoenixProfiler.Endpoint
end
end

defp plug do
# todo: ensure we are within a Phoenix.Endpoint
quote location: :keep do
plug PhoenixProfiler.Plug
end
end

@doc """
Returns the child specification to start the profiler
under a supervision tree.
"""
def child_spec(opts) do
%{
id: opts[:name] || PhoenixProfiler,
start: {PhoenixProfiler.Supervisor, :start_link, [opts]}
start: {PhoenixProfiler.Profiler, :start_link, [opts]}
}
end

@behaviour Plug

@impl Plug
defdelegate init(opts), to: PhoenixProfiler.Plug

@impl Plug
defdelegate call(conn, opts), to: PhoenixProfiler.Plug

# TODO: Remove when we require LiveView v0.17+.
@doc false
def mount(params, session, socket) do
Expand All @@ -39,73 +46,63 @@ defmodule PhoenixProfiler do

"""
def on_mount(_arg, _params, _session, socket) do
{:cont, PhoenixProfiler.Utils.maybe_mount_profile(socket)}
{:cont, PhoenixProfiler.Utils.maybe_mount_profiler(socket)}
end

@doc """
Enables the profiler on a given `conn` or connected `socket`.

Normally you do not need to invoke this function manually. It is invoked
automatically by the PhoenixProfiler plug in the Endpoint when a
profiler is enabled. In LiveView v0.16+ it is invoked automatically when
you define `on_mount PhoenixProfiler` on your LiveView.
Useful when choosing to start a profiler with
`[enable: false]`, but normally you do not need to invoke it
manually.

This function will raise if the endpoint is not configured with a profiler,
or if the configured profiler is not running. For LiveView specifically,
this function also raises if the given socket is not connected.
Note the profiler server must be running and the `conn` or
`socket` must have been configured for profiling for this
function to have any effect.

## Example

Within a Phoenix Controller (for example, on a show callback):
Within a Phoenix Controller (for example on a `show` callback):

def show(conn, params) do
conn = PhoenixProfiler.enable(conn)
# code...
end

Within a LiveView (for example, on the mount callback):

def mount(params, session, socket) do
socket =
if connected?(socket) do
PhoenixProfiler.enable(socket)
else
socket
end
Within a LiveView (for example on a `handle_info` callback):

def handle_info(:debug_me, socket) do
socket = PhoenixProfiler.enable(socket)
# code...
end

"""
defdelegate enable(conn_or_socket), to: PhoenixProfiler.Utils, as: :enable_profiler
defdelegate enable(conn_or_socket), to: PhoenixProfiler.Profiler

@doc """
Disables profiling on a given `conn` or `socket`.

## Examples

Within a Phoenix Controller (for example, on an update callback):
Within a Phoenix Controller (for example on an `update` callback):

def update(conn, params) do
conn = PhoenixProfiler.disable(conn)
# code...
end

Within in a LiveView (for example, on a handle_event callback):
Within in a LiveView (for example on a `handle_event` callback):

def handle_event("some-event", _, socket) do
socket = PhoenixProfiler.disable(socket)
# code...
end

Note that only for LiveView, if you invoke `disable/1` on
the LiveView `mount` callback, the profiler may not be
registered yet and it will not receive the disable message.
If you need on-demand profiling, it is recommended you
start with the profiler in a disabled state and enable it
after the LiveView has mounted.
Note that for LiveView, you must invoke `disable/1` _after_
the LiveView has completed its connected mount for this function
to have any effect.
"""
defdelegate disable(conn_or_socket), to: PhoenixProfiler.Utils, as: :disable_profiler
defdelegate disable(conn_or_socket), to: PhoenixProfiler.Profiler

@doc """
Resets the storage of the given `profiler`.
Expand Down
26 changes: 12 additions & 14 deletions lib/phoenix_profiler/dashboard.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ if Code.ensure_loaded?(Phoenix.LiveDashboard) do

"""
use Phoenix.LiveDashboard.PageBuilder
alias PhoenixProfiler.Profile
alias PhoenixProfiler.ProfileStore
alias PhoenixProfiler.Utils

Expand Down Expand Up @@ -188,7 +189,7 @@ if Code.ensure_loaded?(Phoenix.LiveDashboard) do
end

defp render_panel(:request, assigns) do
conn = assigns.profile.conn
conn = assigns.profile.data.conn

nav_bar(
items: [
Expand Down Expand Up @@ -297,29 +298,26 @@ if Code.ensure_loaded?(Phoenix.LiveDashboard) do
defp fetch_profiles(params, profiler, node) do
%{search: search, sort_by: sort_by, sort_dir: sort_dir, limit: limit} = params

{profiles, total} = fetch_profiles(node, profiler, search, sort_by, sort_dir, limit)
{profiles, total} =
ProfileStore.remote_list_advanced(node, profiler, search, sort_by, sort_dir, limit)

rows =
for {token, prof} <- profiles do
%{at: at, conn: %Plug.Conn{} = conn} = prof
for {token, profile} <- profiles do
%Profile{
data: %{conn: %Plug.Conn{} = conn},
system_time: system_time
} = profile

conn
|> Map.take([:host, :status, :method, :remote_ip])
|> Map.put(:url, Plug.Conn.request_url(conn))
|> Map.put(:token, token)
|> Map.put(:at, at)
|> Map.put(:system_time, system_time)
end

{rows, total}
end

defp fetch_profiles(node, profiler, search, sort_by, sort_dir, limit) do
profiles =
ProfileStore.remote_list_advanced(node, profiler, search, sort_by, sort_dir, limit)

{profiles, length(profiles)}
end

defp columns do
[
%{
Expand All @@ -339,8 +337,8 @@ if Code.ensure_loaded?(Phoenix.LiveDashboard) do
header: "URL"
},
%{
field: :at,
header: "Profiled at",
field: :system_time,
header: "Time",
sortable: :desc,
format: &format_time/1
},
Expand Down
Loading