# `RasterExRatatui.Telemetry`
[🔗](https://github.com/mcass19/raster_ex_ratatui/blob/v0.4.0/lib/raster_ex_ratatui/telemetry.ex#L1)

`:telemetry` integration for `raster_ex_ratatui`.

Mirrors the shape of `ExRatatui.Telemetry` one layer up: events fire at the boundaries this package controls (surface lifecycle, rasterisation, the consumer's `push/2`, input forwarding) rather than at the runtime layer `ex_ratatui` already instruments. Both trees fire concurrently while a surface runs; attach handlers to whichever is needed.

## Events

All events are prefixed with `:raster_ex_ratatui`.

### Span events (`:start` / `:stop` / `:exception`)

| Event | Description | Metadata |
| ----- | ----------- | -------- |
| `[:raster_ex_ratatui, :frame, :raster]` | `RasterExRatatui.Raster.apply/2` on the diffs waiting from the app server. | `:surface`, `:mod`, `:pid`; `:stop` adds `:diffs` (how many were folded into this call), `:cells`, `:regions`, `:patches` |
| `[:raster_ex_ratatui, :frame, :push]` | The consumer's `c:RasterExRatatui.Surface.push/2` call. | `:surface`, `:mod`, `:pid`, `:push_mode` |

`:surface` is the surface module, `:mod` the app module, and `:pid` the surface process, which tells apart several surfaces running the same modules.

`:start` events carry `%{monotonic_time: integer, system_time: integer}` as measurements. `:stop` events add `:duration` (native units). On exception the metadata gains `:kind`, `:reason`, and `:stacktrace`.

### Single events

| Event | Description | Measurements | Metadata |
| ----- | ----------- | ------------ | -------- |
| `[:raster_ex_ratatui, :surface, :start]` | The surface built its raster and started the app server. | `%{system_time: integer}` | `:surface`, `:mod`, `:pid`, `:size`, `:grid_size` |
| `[:raster_ex_ratatui, :surface, :stop]` | The surface is terminating. | `%{system_time: integer}` | `:surface`, `:mod`, `:pid`, `:reason` |
| `[:raster_ex_ratatui, :app, :exit]` | The app server exited; `:action` is what the surface does about it per its `on_app_exit:` option, `:stop` or `:restart`. | `%{system_time: integer}` | `:surface`, `:mod`, `:pid`, `:reason`, `:action` |
| `[:raster_ex_ratatui, :input, :forward]` | An event was forwarded to the app server. | `%{system_time: integer}` | `:surface`, `:mod`, `:pid`, `:event` |

## Attaching a default logger

    RasterExRatatui.Telemetry.attach_default_logger(level: :info)

That attaches a handler logging every `:stop` and single event. Detach with `detach_default_logger/0`.

# `attach_default_logger`

```elixir
@spec attach_default_logger(keyword()) :: :ok | {:error, :already_exists}
```

Attaches a logger that prints every `raster_ex_ratatui` telemetry event. Useful during development; detach with `detach_default_logger/0`.

## Options

  * `:level` — log level (default: `:debug`).
  * `:events` — list of full event names to attach (default: every `:stop`, `:exception`, and single event).

# `detach_default_logger`

```elixir
@spec detach_default_logger() :: :ok | {:error, :not_found}
```

Detaches the logger attached with `attach_default_logger/1`.

# `execute`

```elixir
@spec execute([atom(), ...], map(), map()) :: :ok
```

Emits a single `:telemetry` event rooted at `[:raster_ex_ratatui | event]`.

`:system_time` is added to the measurements when not already present.

## Examples

    iex> RasterExRatatui.Telemetry.execute([:input, :forward], %{}, %{mod: MyApp})
    :ok

# `probe`

```elixir
@spec probe(GenServer.server(), number(), keyword()) :: %{
  raster: map() | nil,
  push: map() | nil,
  mailbox: {non_neg_integer(), non_neg_integer()}
}
```

Watches one surface for `seconds` and reports how it keeps up: for the raster and push spans, how many ran, and their median, 90th percentile, and slowest duration in milliseconds, plus the surface's mailbox length before and after. Meant for an IEx session on the device.

    RasterExRatatui.Telemetry.probe(MyDevice.Surface, 10)
    raster: n=56 in 10s  median=42.6ms  p90=65.0ms  max=70.8ms
    push:   n=56 in 10s  median=12.8ms  p90=15.2ms  max=27.2ms
    surface mailbox: 0 -> 0

Reading it: `raster` is the cost of turning renders into pixels and `push` the cost of the device write, so their sum bounds the frame rate; a raster count well below the app's render rate means renders were folded together (see `[:raster_ex_ratatui, :frame, :raster]`'s `:diffs`); a mailbox that grows during the probe means the surface is falling behind.

Returns the same figures as a map, `%{raster: stats | nil, push: stats | nil, mailbox: {before, after}}`, where stats is `%{n:, median:, p90:, max:}` in milliseconds. Pass `print: false` to skip the report.

# `span`

```elixir
@spec span([atom(), ...], map(), (-&gt; {result, map()})) :: result when result: term()
```

Wraps `fun` in a `:telemetry` span rooted at `[:raster_ex_ratatui | event]`.

`fun` returns `{result, stop_meta}`; `result` is returned and `stop_meta` is merged over `meta` for the `:stop` event, which is how counts known only after the work (patches produced, for instance) reach handlers.

## Examples

    iex> RasterExRatatui.Telemetry.span([:frame, :raster], %{mod: MyApp}, fn -> {:done, %{patches: 3}} end)
    :done

---

*Consult [api-reference.md](api-reference.md) for complete listing*
