VirtualTimeGenServer.Instrumentation behaviour (GenServerVirtualTime v1.0.1)

Copy Markdown View Source

A port for observing what an instrumented process does.

The virtual clock and the server wrappers know how to hand an event to an observer and nothing about what the observer does with it. That separation is the point: measuring a system — counters, traces, diagrams — is a different concern from controlling its time, and a library that owns the clock should not have to know what a counter is.

An observer is installed per process:

Callbacks run inside the observed process, so keeping state is the observer's own business: one observer keeps counters in the process dictionary, another forwards events to a collector process, a third does nothing. The library never inspects that state; it only hands it back from VirtualTimeGenServer.snapshot/1 if the observer produced it.

An observer that raises raises in the process running it, like any other code running there. The library does not swallow the error.

Example

defmodule MessageCounter do
  @behaviour VirtualTimeGenServer.Instrumentation

  @impl true
  def on_receive({_kind, _message}, _meta) do
    Process.put(:received, Process.get(:received, 0) + 1)
    :ok
  end

  @impl true
  def on_send({_kind, _message}, _meta) do
    Process.put(:sent, Process.get(:sent, 0) + 1)
    :ok
  end

  @impl true
  def snapshot(_context) do
    %{sent: Process.get(:sent, 0), received: Process.get(:received, 0)}
  end
end

{:ok, clock} = VirtualClock.start_link()

{:ok, server} =
  VirtualTimeGenServer.start_link(MyServer, :ok,
    virtual_clock: clock,
    instrumentation: MessageCounter
  )

{:ok, %{received: 1}} = VirtualTimeGenServer.snapshot(server)

Summary

Types

A message the observed process handled or sent: the kind of OTP callback (:call, :cast or :info) and the payload.

Where the callback is running and what the observer was installed with.

t()

What start_link/3's :instrumentation option and instrument/1 accept: a module, or a module with the context its callbacks receive.

Callbacks

Called in the observed server process for every message it receives, before the server's own callback runs.

Called in the sending process for every message it sends through VirtualTimeGenServer.call/3 or VirtualTimeGenServer.cast/2.

Called in the observed server process when someone asks VirtualTimeGenServer.snapshot/1 what this observer collected.

Functions

Normalizes an instrumentation spec to nil or {module, context}.

Types

event()

@type event() :: {kind :: :call | :cast | :info, payload :: term()}

A message the observed process handled or sent: the kind of OTP callback (:call, :cast or :info) and the payload.

meta()

@type meta() :: %{
  :context => map(),
  optional(:module) => module(),
  optional(:server) => pid(),
  optional(:sender) => pid()
}

Where the callback is running and what the observer was installed with.

:context is the map the installer passed next to the module; :module is the observed server's own module; :server or :sender is the process the callback runs in.

t()

@type t() :: nil | module() | {module(), map()}

What start_link/3's :instrumentation option and instrument/1 accept: a module, or a module with the context its callbacks receive.

Callbacks

on_receive(event, meta)

@callback on_receive(event(), meta()) :: :ok

Called in the observed server process for every message it receives, before the server's own callback runs.

The event carries the message the server is about to handle — not the library's transport for it. A message the clock delivered at a scheduled virtual time arrives as the message itself.

on_send(event, meta)

@callback on_send(event(), meta()) :: :ok

Called in the sending process for every message it sends through VirtualTimeGenServer.call/3 or VirtualTimeGenServer.cast/2.

The library's own queries (VirtualTimeGenServer.snapshot/1) are not reported: an observer sees the traffic of the observed code only.

snapshot(context)

@callback snapshot(context :: map()) :: term()

Called in the observed server process when someone asks VirtualTimeGenServer.snapshot/1 what this observer collected.

The returned term is the observer's business; the library hands it back unchanged.

Functions

normalize(module)

@spec normalize(t()) :: nil | {module(), map()}

Normalizes an instrumentation spec to nil or {module, context}.

Used by the library to accept both the bare-module and the module-with-context form.