Reads what the toolbar captured, without a browser: from IEx, a remote shell,
or an agent that evaluates code in the running app (such as Tidewave's
project_eval). See ADR 13.
alias PhoenixDevToolbar.Report
Report.latest(path: "/posts") |> Report.summary()
#=> %{queries: %{count: 23, failed: 0, total_us: 4180, slowest: [...]},
# n_plus_one: [%{sql: "SELECT ... WHERE post_id = ?", count: 20, ...}], ...}Sessions and targets
A session is one page view: a dead render request (kind: :request) or a
LiveView process (kind: :live_view). sessions/1 lists them newest first,
latest/1 returns the newest that matches.
The reading functions take a target: a sid, a session from sessions/1, or
a list of sids. A request's target includes the LiveView sessions that joined
from it (its connected mount), so the dead render and the connected mount read
as one page; pass children: false to read the request alone. A LiveView
reached by live navigation is a session of its own.
What you get
Plain maps with atom keys, described by the types below. Durations are in
microseconds, at is a DateTime. Values are already truncated and redacted
when captured: you read what the panel shows. New keys may be added; renaming
or removing one is a breaking change noted in the CHANGELOG.
Limits
Only what the Store still holds is readable: events are capped per session
and expire after the TTL (10 minutes by default, limits: in the config). A
session whose events were dropped or trimmed says so (dropped, trimmed).
While the toolbar is disabled every function returns [] or nil.
Every read runs in the caller's process and copies at most a few sessions' capped events: nothing runs in the host's request or LiveView processes. EXPLAIN and the process actions are not part of this API.
Summary
Types
One LiveView or LiveComponent callback (:mount, :handle_params,
:handle_event, :handle_info, :update, ...). queries and query_us
count the queries run in it, its tasks included. changes is the assigns
diff it produced (see PhoenixDevToolbar.Truncate.changes/0).
A failure: a request exception, a failed callback or render, a failed query.
A repeated query: the same SQL from the same origin run count times in one
callback, in callbacks callbacks so far.
One Ecto query. rows is set on success, error on failure. times holds
Ecto's measurements (:query_time, :queue_time, :decode_time, ...) in
microseconds. origin is the first stacktrace frame outside Ecto (nil unless
the repo has stacktrace: true). span is the callback the query ran in:
see callbacks/2. n_plus_one? marks the query that reached the N+1
threshold and every repetition after it, as the Ecto tab does.
The dead render request: its route and response.
One page view. owner is the LiveView process (nil for a request), parent
the session it came from (the request for a connected mount, the previous
LiveView after live navigation). events is the number stored now.
A session id.
A page at a glance. sids are the sessions read; session is the first.
Each slowest list holds at most 5 items, slowest first.
dropped and trimmed add up the events lost to the caps.
A sid, a session from sessions/1, or a list of sids.
Functions
Every LiveView and LiveComponent callback of target, oldest first. Option: children:.
Every failure of target, oldest first. Option: children:.
The newest session matching sessions/1 options, or nil.
The repeated queries of target, most recent first.
Every query of target, oldest first.
Options: children:, threshold: (see n_plus_one/2).
Lists the sessions the Store holds, newest first.
A page at a glance, or nil when no session of target is left.
Options: children: (see the module docs), threshold: (see n_plus_one/2).
Types
@type callback() :: %{ sid: sid(), seq: pos_integer(), span: non_neg_integer() | nil, at: DateTime.t(), duration_us: non_neg_integer() | nil, callback: atom(), view: module() | nil, component: module() | nil, id: String.t() | nil, event: String.t() | nil, params: String.t() | nil, changes: PhoenixDevToolbar.Truncate.changes() | nil, queries: non_neg_integer(), query_us: non_neg_integer(), error: String.t() | nil }
One LiveView or LiveComponent callback (:mount, :handle_params,
:handle_event, :handle_info, :update, ...). queries and query_us
count the queries run in it, its tasks included. changes is the assigns
diff it produced (see PhoenixDevToolbar.Truncate.changes/0).
@type error() :: %{ sid: sid(), seq: pos_integer(), at: DateTime.t(), source: :request | :live_view | :ecto, message: String.t(), stacktrace: [String.t()] }
A failure: a request exception, a failed callback or render, a failed query.
@type n_plus_one() :: %{ sql: String.t() | nil, origin: String.t() | nil, count: pos_integer(), callbacks: pos_integer(), sids: [sid()] }
A repeated query: the same SQL from the same origin run count times in one
callback, in callbacks callbacks so far.
@type query() :: %{ sid: sid(), seq: pos_integer(), span: non_neg_integer() | nil, at: DateTime.t(), duration_us: non_neg_integer() | nil, repo: String.t() | nil, sql: String.t() | nil, params: String.t() | nil, source: String.t() | nil, rows: non_neg_integer() | nil, error: String.t() | nil, times: %{required(atom()) => non_neg_integer()}, origin: String.t() | nil, stacktrace: [String.t()], n_plus_one?: boolean() }
One Ecto query. rows is set on success, error on failure. times holds
Ecto's measurements (:query_time, :queue_time, :decode_time, ...) in
microseconds. origin is the first stacktrace frame outside Ecto (nil unless
the repo has stacktrace: true). span is the callback the query ran in:
see callbacks/2. n_plus_one? marks the query that reached the N+1
threshold and every repetition after it, as the Ecto tab does.
@type request() :: %{ method: String.t() | nil, path: String.t() | nil, route: String.t() | nil, handler: String.t() | nil, status: non_neg_integer() | nil, duration_us: non_neg_integer() | nil }
The dead render request: its route and response.
@type session() :: %{ sid: sid(), kind: :request | :live_view, parent: sid() | nil, view: module() | nil, path: String.t() | nil, owner: pid() | nil, started_at: DateTime.t(), events: non_neg_integer(), dropped: non_neg_integer(), trimmed: non_neg_integer() }
One page view. owner is the LiveView process (nil for a request), parent
the session it came from (the request for a connected mount, the previous
LiveView after live navigation). events is the number stored now.
@type sid() :: String.t()
A session id.
@type summary() :: %{ sids: [sid()], session: session(), request: request() | nil, queries: %{ count: non_neg_integer(), failed: non_neg_integer(), total_us: non_neg_integer(), slowest: [query()] }, n_plus_one: [n_plus_one()], callbacks: %{ count: non_neg_integer(), total_us: non_neg_integer(), slowest: [callback()] }, renders: %{count: non_neg_integer(), total_us: non_neg_integer()}, errors: [error()], dropped: non_neg_integer(), trimmed: non_neg_integer() }
A page at a glance. sids are the sessions read; session is the first.
Each slowest list holds at most 5 items, slowest first.
dropped and trimmed add up the events lost to the caps.
A sid, a session from sessions/1, or a list of sids.
Functions
Every LiveView and LiveComponent callback of target, oldest first. Option: children:.
Every failure of target, oldest first. Option: children:.
The newest session matching sessions/1 options, or nil.
@spec n_plus_one(target(), keyword()) :: [n_plus_one()]
The repeated queries of target, most recent first.
Options: children:, threshold: (repetitions in one callback, at least 2,
or false). The threshold defaults to the Ecto plugin's n_plus_one: option.
Every query of target, oldest first.
Options: children:, threshold: (see n_plus_one/2).
Lists the sessions the Store holds, newest first.
Options: :path (exact string or a Regex), :view (module), :kind
(:request or :live_view), :limit.
A page at a glance, or nil when no session of target is left.
Options: children: (see the module docs), threshold: (see n_plus_one/2).