https://github.com/codepr/pluribus
Small library to simplify testing of scalable platforms, e.g. MQTT platforms for IoT, it allows to define custom behaviors and deploy virtual devices in a cluster of nodes.
https://github.com/codepr/pluribus
Last synced: 5 months ago
JSON representation
Small library to simplify testing of scalable platforms, e.g. MQTT platforms for IoT, it allows to define custom behaviors and deploy virtual devices in a cluster of nodes.
- Host: GitHub
- URL: https://github.com/codepr/pluribus
- Owner: codepr
- Created: 2025-10-31T22:17:28.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2025-11-07T10:22:34.000Z (8 months ago)
- Last Synced: 2025-11-07T12:13:52.776Z (8 months ago)
- Language: Elixir
- Homepage:
- Size: 49.8 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Pluribus
Small library to simplify testing of scalable platforms, e.g. MQTT platforms
for IoT, it allows to define custom behaviors and deploy virtual devices in a
cluster of nodes.
Old buried library I wrote ages ago to test out an IoT infra, resurrected and
re-factored a little.
## Roadmap
Currently very rough and ready stage, next mid / long term improvements:
- Better device lifecycle management (e.g. `on_start`, `on_stop` callbacks etc)
- Fleet scaling
- Load strategies (e.g. wave, random burts, ramp up etc)
- Network issues simulation
- Automated scenarios in a test-like DSL
- Dashboard to observe the fleets, load etc
- Pre-built aggregators (e.g. generic MQTT, HTTP / REST, FIX etc)
## Quickstart
Deploy a device in the cluster. The device process will be started in any node of
the cluster.
Allowed option entries:
- `device_id` represents the ID of a virtual device, if not specified a random one
will be generated
- `aggregator_module` the logic for publishing telemetries produced by the virtual
device, may be any I/O, e.g writing to a DB, to a broker etc.
By default if not specified uses the `ConsoleTelemetryAggregator` or the one
defined in the config module.
```elixir
iex> Pluribus.deploy_device(Pluribus.VirtualDevices.GenericVirtualDevice)
{:ok, <123>}
```
### Custom aggregator module
The following example shows how to define a MQTT (uses [emqtt](https://github.com/emqx/emqtt))
based custom device and aggregator
```elixir
defmodule MQTTTelemetryAggregator do
use GenServer
@behaviour Pluribus.TelemetryAggregator
def start_link(args) do
GenServer.start_link(__MODULE__, args)
end
# --- TELEMETRY AGGREGATOR CALLBACKS ---
@impl true
def publish_telemetry(telemetry) do
payload = :erlang.term_to_binary(message)
GenServer.cast(__MODULE__, {:publish, payload})
end
# --- GENSERVER CALLBACKS ---
@impl true
def init(args) do
{:ok, pid} = :emqtt.start_link(args)
{:ok, %{pid: pid}, {:continue, :start_emqtt}}
end
@impl true
def handle_continue(:start_emqtt, %{pid: pid} = state) do
{:ok, _} = :emqtt.connect(pid)
emqtt_opts = Application.get_env(:aggregator_module, :emqtt)
clientid = emqtt_opts[:clientid]
report_topic = "reports/\#{clientid}/temperature"
{:noreply, %{state | report_topic: report_topic}}
end
@impl true
def handle_cast({:publish, payload}, state) do
%{pid: pid, report_topic: report_topic} = state
:emqtt.publish(pid, report_topic, payload)
{:noreply, state}
end
end
```
### Custom virtual device
Handles some state and updates from subscriptions
```elixir
defmodule MQTTVirtualDevice do
use GenServer
@behaviour Pluribus.VirtualDeviceState
def start_link(args) do
GenServer.start_link(__MODULE__, args)
end
# --- GENSERVER CALLBACKS ---
@impl true
def init(args) do
{:ok, pid} = :emqtt.start_link(args)
{:ok, %{pid: pid}, {:continue, :start_emqtt}}
end
@impl true
def handle_continue(:start_emqtt, %{pid: pid} = state) do
{:ok, _} = :emqtt.connect(pid)
emqtt_opts = Application.get_env(:virtual_device, :emqtt)
clientid = emqtt_opts[:clientid]
metrics_topic = "metrics/\#{clientid}/temperature"
{:ok, _, _} = :emqtt.subscribe(pid, {"metrics/\#{clientid}/temperature", 1})
{:noreply, %{state | metrics_topic: metrics_topic}}
end
@impl true
def handle_info({:publish, %{payload: payload}}, state) do
{:noreply, %{state | metrics: :erlang.binary_to_term(payload)}}
end
@impl true
def handle_call(:get_metrics, _from, %{metrics: metrics} = state) do
{:reply, metrics, state}
end
# --- VIRTUAL DEVICE CALLBACKS ---
@impl true
def report_telemetry(state) do
metrics = get_metrics()
%{
device_id: state.id,
device_type: "MQTT_Sensor",
timestamp: System.os_time(:millisecond),
data: metrics
}
end
defp get_metrics do
GenServer.call(__MODULE__, :get_metrics)
end
end
```
### Deploy an MQTT virtual device
```elixir
iex> Pluribus.deploy_device(MQTTVirtualDevice, aggregator_module: MQTTTelemetryAggregator)
{:ok, <123>}
```
### Deploy an MQTT virtual device fleet
```elixir
iex> Pluribus.deploy_fleet([
%{
device_id: "fleet_1_1",
logic_module: MQTTVirtualDevice,
aggregator_module: MQTTTelemetryAggregator,
opts: [{:clientid, "fleet_1_1"}]
},
%{
device_id: "fleet_1_2",
logic_module: MQTTVirtualDevice,
aggregator_module: MQTTTelemetryAggregator,
opts: [{:clientid, "fleet_1_2"}]
},
%{
device_id: "fleet_1_3",
logic_module: MQTTVirtualDevice,
aggregator_module: MQTTTelemetryAggregator,
opts: [{:clientid, "fleet_1_3"}]
},
])
```
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `pluribus` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:pluribus, "~> 0.2.0"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at .