Packages

Erlang bindings for OMQ, a Rust ZeroMQ-compatible messaging stack

Current section

Files

Jump to
omq DEVELOPMENT.md
Raw

DEVELOPMENT.md

# OMQ.beam Development
Run commands from repository root unless shown otherwise.
## Prerequisites
- Erlang/OTP 27 or newer.
- `rebar3`.
- Elixir 1.18 or newer for the Elixir wrapper, `erlzmq`, and `chumak`
benchmark peers.
- Gleam built at `~/src/gleam/target/release/gleam` or available on `PATH`.
- Rust toolchain with Cargo.
- Python 3 for benchmarks and chart generation.
- `pyzmq` for TCP interop tests.
## Tests
Erlang API and NIF tests:
```sh
(cd bindings/beam && rebar3 eunit)
```
Rust NIF checks:
```sh
(cd bindings/beam/native && cargo fmt --check && cargo check)
(cd bindings/beam/native && cargo check --no-default-features)
(cd bindings/beam/native && cargo clippy --all-targets)
(cd bindings/beam/native && cargo clippy --all-targets --no-default-features)
```
Default native features are `plain`, `curve`, `lz4`, and `zstd`. The
`--no-default-features` check keeps runtime `has/1` detection honest.
Elixir wrapper:
```sh
(cd bindings/beam/elixir && \
mix format --check-formatted lib/omq.ex ../scripts/bench_peer.exs && \
mix compile --warnings-as-errors && \
mix test)
```
Gleam wrapper:
```sh
(cd bindings/beam/gleam && \
~/src/gleam/target/release/gleam format --check src/omq_gleam.gleam && \
~/src/gleam/target/release/gleam check && \
~/src/gleam/target/release/gleam test)
```
## Soak
Build once, then run the Erlang soak harness. Argument is duration in seconds
per scenario.
```sh
(cd bindings/beam && rebar3 compile)
(cd bindings/beam && escript scripts/soak.erl 300)
```
The harness currently runs sustained PUSH/PULL, REQ/REP cycles, and TCP peer
churn. It samples RSS, VmData, file descriptors, and thread count throughout
each scenario and fails on sustained RSS/FD growth or final FD/thread growth.
Use `600` or `1800` for longer 10 minute and 30 minute passes.
Useful resource knobs:
- `OMQ_BEAM_SOAK_REPORT_INTERVAL_SECS`
- `OMQ_BEAM_SOAK_SETTLE_MS`
- `OMQ_BEAM_SOAK_MAX_FD_GROWTH`
- `OMQ_BEAM_SOAK_MAX_FINAL_FD_GROWTH`
- `OMQ_BEAM_SOAK_MAX_THREAD_GROWTH`
- `OMQ_BEAM_SOAK_MAX_FINAL_THREAD_GROWTH`
- `OMQ_BEAM_SOAK_RSS_SLOPE_LIMIT_KIB_S`
- `OMQ_BEAM_SOAK_RSS_TAIL_GROWTH_PERCENT`
- `OMQ_BEAM_SOAK_TRACE_FD=1`
## Benchmarks
The benchmark script builds the Erlang app, builds the Rust NIF in release
mode, copies the release shared object into `priv/`, compiles Elixir, and
builds Gleam.
Quick run:
```sh
python3 bindings/beam/scripts/update_perf.py --quick
```
Full default run:
```sh
python3 bindings/beam/scripts/update_perf.py
```
Run selected implementations and sizes:
```sh
python3 bindings/beam/scripts/update_perf.py \
--impl omq-erlang,omq-elixir,omq-gleam,erlzmq,chumak \
--sizes 16,128,1024,4096,32768
```
Results append to:
```text
~/.cache/omq.beam/<lang>/bindings.jsonl
```
where `<lang>` is `erlang`, `elixir`, or `gleam`.
`erlzmq` is installed through Hex as `erlzmq_dnif`. `chumak` is installed
through Hex as `chumak`. Chumak PUSH/PULL throughput is capped at 512 B in
this harness because its large-frame stream path can crash the BEAM VM under
the two-process timed benchmark. `exzmq` is not benchmarked: it has no Hex
package, its GitHub package is marked work-in-progress, and its API only
covers CLIENT/SERVER with stdout logging.
## Chart
Regenerate SVG from cached rows:
```sh
python3 bindings/beam/scripts/update_perf.py --chart-only
```
Output:
```text
bindings/beam/doc/charts/bindings.svg
```
The script reads repo-root `.chart_hw` when present:
```text
prefix=Linux VM on a 2018 Mac Mini
postfix=6 cores, performance governor, turbo off
```
Useful flags:
- `--quick`
- `--no-build`
- `--no-chart`
- `--chart-only`
- `--impl a,b,c`
- `--sizes 16,128,1024,4096,32768`
- `--rounds N`
- `--duration SECONDS`
- `--warmup-duration SECONDS`
- `--latency-duration SECONDS`
- `--latency-warmup-duration SECONDS`
- `--timeout SECONDS`
## Publishing
Hex package names:
- `omq`: Erlang base package. Owns the Rust NIF and native OMQ runtime.
- `omq_elixir`: Elixir wrapper. Depends on `omq`.
- `omq_gleam`: Gleam wrapper. Depends on `omq`.
Publish in that order. The wrapper packages cannot resolve until `omq` is
published on Hex.
One-time local setup:
```sh
mix local.hex --force
mix hex.user auth
mkdir -p ~/.config/rebar3
$EDITOR ~/.config/rebar3/rebar.config
rebar3 hex user auth
~/src/gleam/target/release/gleam hex authenticate
```
No standalone `hex` command is needed. Mix, Rebar3, and Gleam each publish
through their own Hex tasks.
Add this line to `~/.config/rebar3/rebar.config` if it is not already there:
```erlang
{plugins, [rebar3_hex]}.
```
Dry-run/audit the Erlang package:
```sh
(cd bindings/beam && rebar3 compile)
(cd bindings/beam && rebar3 hex build)
```
Publish `omq`:
```sh
(cd bindings/beam && rebar3 hex publish)
```
After Hex shows `omq` 0.1.0, dry-run/audit and publish the Elixir wrapper:
```sh
(cd bindings/beam/elixir && mix deps.get)
(cd bindings/beam/elixir && mix compile --warnings-as-errors)
(cd bindings/beam/elixir && mix hex.build --unpack)
(cd bindings/beam/elixir && mix hex.publish)
```
After Hex shows `omq` 0.1.0, dry-run/audit and publish the Gleam wrapper:
```sh
(cd bindings/beam/gleam && ~/src/gleam/target/release/gleam update)
(cd bindings/beam/gleam && ~/src/gleam/target/release/gleam check)
(cd bindings/beam/gleam && ~/src/gleam/target/release/gleam test)
(cd bindings/beam/gleam && ~/src/gleam/target/release/gleam export hex-tarball)
(cd bindings/beam/gleam && ~/src/gleam/target/release/gleam publish)
```
For token-based publishing, Mix reads `HEX_API_KEY`; Gleam reads
`HEXPM_API_KEY`. Prefer the interactive commands above for the first publish
so package metadata and included files can be reviewed before confirming.