PhoenixDevToolbar.Report (PhoenixDevToolbar v0.0.2)

Copy Markdown View Source

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

callback()

@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).

error()

@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.

n_plus_one()

@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.

query()

@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.

request()

@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.

session()

@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.

sid()

@type sid() :: String.t()

A session id.

summary()

@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.

target()

@type target() :: sid() | session() | [sid()]

A sid, a session from sessions/1, or a list of sids.

Functions

callbacks(target, opts \\ [])

@spec callbacks(target(), keyword()) :: [callback()]

Every LiveView and LiveComponent callback of target, oldest first. Option: children:.

errors(target, opts \\ [])

@spec errors(target(), keyword()) :: [error()]

Every failure of target, oldest first. Option: children:.

latest(opts \\ [])

@spec latest(keyword()) :: session() | nil

The newest session matching sessions/1 options, or nil.

n_plus_one(target, opts \\ [])

@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.

queries(target, opts \\ [])

@spec queries(target(), keyword()) :: [query()]

Every query of target, oldest first. Options: children:, threshold: (see n_plus_one/2).

sessions(opts \\ [])

@spec sessions(keyword()) :: [session()]

Lists the sessions the Store holds, newest first.

Options: :path (exact string or a Regex), :view (module), :kind (:request or :live_view), :limit.

summary(target, opts \\ [])

@spec summary(target(), keyword()) :: summary() | nil

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).