StatifierPersistence.Telemetry (StatifierPersistence v0.8.0)

Copy Markdown View Source

The :telemetry surface for this package's storage-phase seams (ADR-0009) - the single definition site for every [:statifier_persistence, ...] event name, and the one module in lib/ that calls :telemetry.execute/3.

docs/telemetry.md is the full contract: what each event answers, what it deliberately leaves to statifier-ex and to opentelemetry_ecto, and what opentelemetry_statifier does with it. This moduledoc is the reference table; that note is the reasoning.

events/0 returns every name below, built from the same literal-atom lists the emitters use, so the bridge can attach one handler per event name without hand-copying the list (ADR-0009 decision 8, ots-ADR-0003).

This module owns family two only. The interpreter's own family - [:statifier, :session, ...] with driver: :persistence - is emitted by calling Statifier.Telemetry directly from the stepper seam and is deliberately not wrapped here: a wrapper would be the second implementation st-ADR-0067 decision 2 exists to prevent.

Structural rules (ADR-0009 decisions 3, 5, 8, 9)

  • The prefix is [:statifier_persistence, ...], fixed and not configurable. The bridge must name the events at compile time, and a per-host prefix would make its attach list depend on host configuration it cannot see.
  • Measurements are numbers; metadata is everything else, integer indexes included - child_index is metadata, because an opaque index has no numeric meaning to average.
  • The step seam is the one :start/:stop pair. This package owns an interval nobody else measures - lock, load, decode, identity-check, advance, execute effects, persist - and the upstream macrostep span nests inside it. span_ref is a fresh make_ref/0 per span, carried on both halves, and is the only pairing key (st-ADR-0040 decision 2). Everything else is a single point-in-time event.
  • run_id is the identity key, never scope; session_id rides only where a position has already been decoded and is explicitly nil otherwise.
  • Emission is unconditional. There is no config knob and no sampling knob: :telemetry.execute/3 on an event with no handlers is a lookup and a return.
  • Amendment discipline. Adding a measurement or a metadata key to an existing event is an amendment and is fine; renaming or removing one, renaming an event, or changing the :persistence driver atom is breaking and needs a new ADR.

The step seam

Brackets one serialized drive - create/4, step/5, fail/4 or cancel/3 inside StatifierPersistence.Runs's own serialized/5. Emitted on the calling process. The [:statifier, :session, :macrostep, ...] span opens and closes inside it.

EventMeasurementsMetadata
[:statifier_persistence, :run, :step, :start]system_time, monotonic_timerun_id, entry, span_ref
[:statifier_persistence, :run, :step, :stop]duration, monotonic_timerun_id, session_id, content_hash, entry, outcome, status, reason, span_ref
[:statifier_persistence, :run, :lock]duration, system_timerun_id, strategy, outcome, reason

entry is which public door was used: :create, :step, :done_invocation, :failed_invocation, :answer_parent, :fail, :cancel. outcome on the stop is :ok, :discarded or :error. [:statifier_persistence, :run, :lock]'s duration is the wait for the per-run exclusion, not the held time, and its outcome is :acquired or :unavailable.

The storage seam

EventMeasurementsMetadata
[:statifier_persistence, :adapter, :call]duration, system_timeadapter, callback, outcome, reason, run_id, session_id, content_hash
[:statifier_persistence, :identity, :refused]system_timerun_id, session_id, stage, reason, stored_content_hash, supplied_content_hash

callback is the StatifierPersistence.Storage.Adapter callback name, a closed vocabulary fixed by the behaviour. stage on a refusal is :position, :run or :chart, and reason is :identity_mismatch or :unidentified_chart; only the two content hashes travel, never the Statifier.Machine.Identity structs the error term carries.

The run lifecycle seam

EventMeasurementsMetadata
[:statifier_persistence, :run, :created]system_timerun_id, session_id, content_hash, child?, metadata?
[:statifier_persistence, :run, :terminated]system_timerun_id, session_id, content_hash, status, driven_by, reason
[:statifier_persistence, :run, :discarded]system_timerun_id, entry, reason, repaired?
[:statifier_persistence, :effect, :failed]system_timerun_id, session_id, content_hash, kind, executor, reason, reentered?
[:statifier_persistence, :drive, :turns_exhausted]system_time, turnsrun_id, entry

driven_by on :terminated is :chart or :host - fail/4 and cancel/3 are the :host ones, and upstream emits nothing at all for them. :discarded's reason is the closed vocabulary :terminal_run, :builder_declined, :position_terminal, and only the third sets repaired?: true.

The durable-subchart seam (ADR-0008)

EventMeasurementsMetadata
[:statifier_persistence, :child, :started]system_timeparent_run_id, child_run_id, invoke_id, child_index, content_hash, session_id
[:statifier_persistence, :child, :refused]system_timeparent_run_id, invoke_id, reason
[:statifier_persistence, :child, :answered]system_timechild_run_id, parent_run_id, invoke_id, outcome
[:statifier_persistence, :child, :cascade_cancelled]system_time, count, retainedparent_run_id, invoke_id

content_hash on :started is the child's pinned hash (ADR-0008 decision 2). count on :cascade_cancelled is how many runs the sweep actually cancelled and retained is how many it found already terminal and left alone; both are legitimately 0.

Cardinality and disclosure

Every metadata key is bounded by the chart or by a closed vocabulary except run_id (and parent_run_id / child_run_id), which is host-supplied and is a correlation id for a span or a log line, never a metric dimension, and reason, which carries an arbitrary executor or adapter term on some events and must be narrowed before it becomes a dimension.

Nothing host-opaque and nothing from the datamodel is ever on an event (ADR-0009 decision 7): not the chart_blob, the position_blob, the identity_blob, the ADR-0006 metadata map, the datamodel, an invoke's params, or a :done effect's donedata. metadata? on [:statifier_persistence, :run, :created] is a boolean - whether a non-empty host map was supplied - and that is the whole of what this contract says about it.

Summary

Types

Any event name this module emits.

A field list for one event: every key the contract names for it, in any order. A key the caller omits is emitted as nil rather than dropped, so a handler never has to Map.get/3 its way around a shape that varies.

Functions

Emits [:statifier_persistence, :adapter, :call], duration in :native units around one storage-adapter callback.

Emits [:statifier_persistence, :child, :answered].

Emits [:statifier_persistence, :child, :cascade_cancelled] once per public StatifierPersistence.Runs.cascade_cancel/3 call, after the whole sweep - never once per node of the walk.

Emits [:statifier_persistence, :child, :refused].

Emits [:statifier_persistence, :child, :started].

Emits [:statifier_persistence, :drive, :turns_exhausted] - the drive loop's own refusal, reported as a point-in-time verdict rather than a span (ADR-0009 decision 5).

Emits [:statifier_persistence, :effect, :failed] - the executor seam's verdict on one effect it accepted and could not perform.

Every [:statifier_persistence, ...] event name this package emits, in the order docs/telemetry.md tables them.

Emits [:statifier_persistence, :identity, :refused] - the deploy-drift alarm.

Emits [:statifier_persistence, :run, :created].

Emits [:statifier_persistence, :run, :discarded].

Emits [:statifier_persistence, :run, :lock]. duration is the wait for the per-run exclusion in :native units, never the held time.

Emits [:statifier_persistence, :run, :step, :start] and returns the System.monotonic_time/0 reading run_step_stop/2 measures duration against.

Emits [:statifier_persistence, :run, :step, :stop], duration in :native units measured from run_step_start/3's reading.

Emits [:statifier_persistence, :run, :terminated]. driven_by is :chart for a :done/:budget_exhausted termination and :host for StatifierPersistence.Runs.fail/4 and cancel/3, which no interpreter runs on and which upstream therefore never reports.

Types

event_name()

@type event_name() :: [atom(), ...]

Any event name this module emits.

fields()

@type fields() :: keyword()

A field list for one event: every key the contract names for it, in any order. A key the caller omits is emitted as nil rather than dropped, so a handler never has to Map.get/3 its way around a shape that varies.

Functions

adapter_call(duration, fields)

@spec adapter_call(duration :: integer(), fields :: fields()) :: :ok

Emits [:statifier_persistence, :adapter, :call], duration in :native units around one storage-adapter callback.

child_answered(fields)

@spec child_answered(fields :: fields()) :: :ok

Emits [:statifier_persistence, :child, :answered].

child_cascade_cancelled(count, retained, fields)

@spec child_cascade_cancelled(
  count :: non_neg_integer(),
  retained :: non_neg_integer(),
  fields :: fields()
) :: :ok

Emits [:statifier_persistence, :child, :cascade_cancelled] once per public StatifierPersistence.Runs.cascade_cancel/3 call, after the whole sweep - never once per node of the walk.

child_refused(fields)

@spec child_refused(fields :: fields()) :: :ok

Emits [:statifier_persistence, :child, :refused].

child_started(fields)

@spec child_started(fields :: fields()) :: :ok

Emits [:statifier_persistence, :child, :started].

drive_turns_exhausted(turns, fields)

@spec drive_turns_exhausted(turns :: non_neg_integer(), fields :: fields()) :: :ok

Emits [:statifier_persistence, :drive, :turns_exhausted] - the drive loop's own refusal, reported as a point-in-time verdict rather than a span (ADR-0009 decision 5).

effect_failed(fields)

@spec effect_failed(fields :: fields()) :: :ok

Emits [:statifier_persistence, :effect, :failed] - the executor seam's verdict on one effect it accepted and could not perform.

Nothing wraps a successful executor call: the host's work is the host's to instrument, and the step span already bounds it.

events()

@spec events() :: [event_name(), ...]

Every [:statifier_persistence, ...] event name this package emits, in the order docs/telemetry.md tables them.

This is what a bridge attaches to: ots-ADR-0003 attaches one handler per event name under its own handler id, and it cannot do that for a list it has to hand-copy.

identity_refused(fields)

@spec identity_refused(fields :: fields()) :: :ok

Emits [:statifier_persistence, :identity, :refused] - the deploy-drift alarm.

Only the two content hashes ever travel, never the Statifier.Machine.Identity structs an {:identity_mismatch, _, _} term carries (ADR-0009 decision 7).

run_created(fields)

@spec run_created(fields :: fields()) :: :ok

Emits [:statifier_persistence, :run, :created].

run_discarded(fields)

@spec run_discarded(fields :: fields()) :: :ok

Emits [:statifier_persistence, :run, :discarded].

run_lock(duration, fields)

@spec run_lock(duration :: integer(), fields :: fields()) :: :ok

Emits [:statifier_persistence, :run, :lock]. duration is the wait for the per-run exclusion in :native units, never the held time.

run_step_start(run_id, entry, span_ref)

@spec run_step_start(run_id :: term(), entry :: atom(), span_ref :: reference()) ::
  integer()

Emits [:statifier_persistence, :run, :step, :start] and returns the System.monotonic_time/0 reading run_step_stop/2 measures duration against.

Returning the reading rather than taking one is deliberate: it is the same reading the monotonic_time measurement carries, so the span's duration and the two halves' monotonic_time values cannot drift apart.

run_step_stop(start_time, fields)

@spec run_step_stop(start_time :: integer(), fields :: fields()) :: :ok

Emits [:statifier_persistence, :run, :step, :stop], duration in :native units measured from run_step_start/3's reading.

run_terminated(fields)

@spec run_terminated(fields :: fields()) :: :ok

Emits [:statifier_persistence, :run, :terminated]. driven_by is :chart for a :done/:budget_exhausted termination and :host for StatifierPersistence.Runs.fail/4 and cancel/3, which no interpreter runs on and which upstream therefore never reports.