kino_ex_ratatui emits :telemetry events at the boundaries the widget itself controls. They sit one layer above the events ex_ratatui already emits — together they give you a complete profile of a Livebook-driven TUI.

Event tree at a glance

[:kino_ex_ratatui, :transport, :connect]    span     widget boots Session + runtime
[:kino_ex_ratatui, :transport, :disconnect] single   server :DOWN or widget terminate
[:kino_ex_ratatui, :render, :frame]         span     ANSI broadcast over the Kino bridge
[:kino_ex_ratatui, :input, :forward]        single   bytes from xterm.js  runtime
[:kino_ex_ratatui, :resize]                 single   resizes after the boot one

Span events emit :start / :stop / :exception suffixes. Most handlers only attach to :stop for timing and :exception for failures.

See Kino.ExRatatui.Telemetry for the full metadata reference.

Quick start: log every event

# In a Livebook setup cell, or your application's start/2 callback:
Kino.ExRatatui.Telemetry.attach_default_logger(level: :info)

Detach with Kino.ExRatatui.Telemetry.detach_default_logger/0.

Wiring Telemetry.Metrics

If you're already running Telemetry.Metrics (e.g. in a Phoenix LiveDashboard), add these alongside whatever ex_ratatui metrics you care about:

defmodule MyApp.Telemetry do
  import Telemetry.Metrics

  def metrics do
    [
      # Time-to-first-frame on widget boot.
      summary("kino_ex_ratatui.transport.connect.stop.duration",
        unit: {:native, :millisecond}
      ),

      # How many sessions opened / closed.
      counter("kino_ex_ratatui.transport.disconnect"),

      # Per-frame ANSI broadcast cost (typically microseconds).
      summary("kino_ex_ratatui.render.frame.stop.duration",
        unit: {:native, :microsecond}
      ),

      # Input bytes flowing client → server.
      sum("kino_ex_ratatui.input.forward.byte_count"),

      # Resize storms (windows, splits, …).
      counter("kino_ex_ratatui.resize")
    ]
  end
end

Pairing with ex_ratatui's events

The two trees are complementary, not duplicative:

ConcernOwned by
mount/1 runtime, App handle_event/2, render command building[:ex_ratatui, ...]
Widget boot (network handshake + first Transport.start_server/1), ANSI broadcast over the Kino bridge, client input forwarding[:kino_ex_ratatui, ...]

Attach to whichever you need. A typical setup attaches [:ex_ratatui, :runtime, :event, :stop] for App-level latency and [:kino_ex_ratatui, :render, :frame, :stop] for the wire cost — together you can spot where time is going without instrumenting either layer manually.

Custom handlers

The public helpers Kino.ExRatatui.Telemetry.span/3 and Kino.ExRatatui.Telemetry.execute/3 are thin wrappers around :telemetry.span/3 and :telemetry.execute/3 that prepend :kino_ex_ratatui to the event name. Use them if you're building a higher-level wrapper around Kino.ExRatatui.new/2 and want to emit your own events under the same namespace.

For one-off handlers, attach directly:

:telemetry.attach(
  "my-app-frame-tracker",
  [:kino_ex_ratatui, :render, :frame, :stop],
  &MyApp.TuiTracker.handle_frame/4,
  nil
)

Always use a captured module function (&MyApp.TuiTracker.handle_frame/4), not an anonymous function — :telemetry logs a performance warning otherwise.