phi_accrual_udp

Dedicated UDP socket source for phi_accrual. Escapes BEAM distribution head-of-line blocking that affects the bundled PhiAccrual.Source.DistributionPing reference source.

⚠️ Alpha — v0.1.x. Public API and wire format may change before v1.0 based on real-deployment feedback. The packet format is deliberately conservative (magic + version + flags) to enable future evolution without breaking on-the-wire compatibility.

Why a separate package

The core phi_accrual library is intentionally transport-agnostic. Heartbeat transports live in their own packages so consumers can mix and match — UDP for decision-grade detection, BEAM distribution for observability-grade, custom transports for application-specific signals. See the phi_accrual roadmap for the ecosystem rationale.

Quick start

# mix.exs
def deps do
  [
    {:phi_accrual, "~> 1.0"},
    {:phi_accrual_udp, "~> 0.1"}
  ]
end

In your supervision tree:

children = [
  {PhiAccrualUdp.Listener, port: 4370},
  {PhiAccrualUdp.Sender,
    targets: [{{10, 0, 0, 2}, 4370}, {{10, 0, 0, 3}, 4370}],
    interval_ms: 1_000}
]

Wire format (v1, 12 bytes fixed)

<<magic::16, version::8, flags::8, timestamp::64-unsigned>>

magic     = 0xCEA6   (identifies a phi_accrual UDP heartbeat)
version   = 0x01     (this format)
flags     = 0x00     (reserved, must be zero in v1)
timestamp = u64 ms   (sender's choice of clock; diagnostic only)

The receiver does not use the packet timestamp for the EWMA — it uses local monotonic receipt time, preserving phi_accrual's clock discipline. The packet timestamp is diagnostic-only (e.g., one-way delay computation when NTP-synced).

Telemetry

[:phi_accrual_udp, :listener, :started]
  metadata: %{port}

[:phi_accrual_udp, :sample, :received]
  measurements: %{packet_timestamp_ms}
  metadata:     %{node, peer}

[:phi_accrual_udp, :decode, :error]
  measurements: %{packet_size}
  metadata:     %{reason, peer}
  # reason ∈ [:wrong_size, :bad_magic, :unsupported_version, :reserved_flags_set]

[:phi_accrual_udp, :sender, :started]
  metadata: %{interval_ms, target_count}

[:phi_accrual_udp, :sender, :tick]
  measurements: %{sent, errors}

Security

UDP is unauthenticated. Anyone who can reach the listener port can send packets that pass Packet.decode/1 and corrupt detection. In hostile networks: bind to a private interface, firewall the port, or layer authentication via a node_resolver that rejects unknown peers.

License

Apache-2.0.