# `Emily.Telemetry`
[🔗](https://github.com/ausimian/emily/blob/1.0.0/lib/emily/telemetry.ex#L1)

`:telemetry` events emitted by Emily.

All span-style events use `:telemetry.span/3` semantics, so attaching
to `*:start`, `*:stop`, and `*:exception` is sufficient for
histograms and error tracking.

## Events

### Evaluation boundaries

`[:emily, :eval, :start | :stop | :exception]` — `Emily.eval/1`.
The `:stop` event carries `:duration` (monotonic native units).

`[:emily, :to_binary, :start | :stop | :exception]` — fires for both
`Emily.to_binary/1` and the `Nx.to_binary/1` path on `Emily.Backend`.
Metadata: `:byte_size`, `:shape`, `:dtype`.

### Fallback entry

`[:emily, :fallback, :start | :stop | :exception]` — fires whenever
an op routes through `Nx.BinaryBackend` because the MLX path is not
wired. Metadata: `:op`, `:input_shapes`, `:input_dtypes`.

Per-fallback behaviour is configurable via `:fallback`:

    config :emily, fallback: :silent | :warn | :raise

  * `:silent` (default) — span events still fire, no log, no raise.
    Library consumers and CI logs stay quiet.
  * `:warn` — one-shot `Logger.warning` per `{op, input_shapes}`
    pair. A Bumblebee user sees
    `"indexed_put on shape [...] fell back to Nx.BinaryBackend"`
    once per shape, not every forward pass. Typically set in
    `config/dev.exs` while chasing performance regressions.
  * `:raise` — raises a `RuntimeError` carrying the op, input
    shapes, and input dtypes. Use in CI to fail builds when a hot
    path unexpectedly routes through `Nx.BinaryBackend`.

In `:raise` mode the `:start`/`:stop` span events do **not** fire
because the raise happens on entry; `:silent` and `:warn` preserve
the full span.

The legacy `:warn_on_fallback` boolean is still honoured when
`:fallback` is unset (`true` → `:warn`, `false` → `:silent`).
Prefer `:fallback` in new code; if both are set, `:fallback` wins.

### Block-dispatch fallback

`[:emily, :block, :fallback]` — a discrete event (not a span) fired
each time the backend's `block` callback (`Nx.Block.*` dispatch,
Nx 0.12+) falls through to Nx's supplied default `fun` rather than a
wired native handler. Measurements: none (`%{}`). Metadata:
`:struct` (the `Nx.Block.*` struct module) and `:args_count`. Use it
in soak runs to flag a block op that used to lower natively but now
lands on the composed-defn slow path.

### Native-compiler fallback

`[:emily, :compiler, :fallback]` — a discrete event (not a span) that
fires when a `native: true` defn can't be lowered by the Expr compiler
and routes through `Nx.Defn.Evaluator` instead (see
`Emily.Compiler`'s `:native_fallback` option). Measurements:
`:count` (always `1`). Metadata: `:key` (the JIT key) and `:reason`
(the lowering error message, which names the unsupported op or
construct). A one-shot `Logger.warning` per distinct `:reason` is also
emitted — set `config :emily, :native_fallback, :raise` to fail
instead of falling back.

### Memory stats (poll-driven)

`[:emily, :memory, :stats]` — discrete event, not a span. Call
`Emily.Memory.stats/0` to sample; measurements:

  * `:active` — bytes currently held by MLX
  * `:peak` — high-water mark since last `Emily.Memory.reset_peak/0`
  * `:cache` — bytes cached for reuse

Wire this into a periodic task (e.g. `Process.send_after/3` loop) to
graph memory drift in a long-running serving.

## Attaching a handler

    :telemetry.attach(
      "emily-fallback-log",
      [:emily, :fallback, :stop],
      fn _event, measurements, metadata, _config ->
        IO.inspect({metadata.op, measurements.duration})
      end,
      nil
    )

# `memory_stats`

```elixir
@spec memory_stats() :: %{
  active: non_neg_integer(),
  peak: non_neg_integer(),
  cache: non_neg_integer()
}
```

Sample the MLX allocator and emit `[:emily, :memory, :stats]`.

Prefer `Emily.Memory.stats/0` in new code. This function remains as
the telemetry-oriented entry point for existing callers.

Returns the measurements map so callers can also log or plot
inline.

## Examples

    iex> stats = Emily.Telemetry.memory_stats()
    iex> Map.keys(stats) |> Enum.sort()
    [:active, :cache, :peak]

---

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