Add week 4 materials

This commit is contained in:
2026-10-01 22:08:30 +03:00
parent 6210b46c1f
commit 6008cff9f6
21 changed files with 1707 additions and 0 deletions
@@ -0,0 +1,42 @@
import random
from anysystem import Context, Message, Process
class Peer(Process):
def __init__(self, proc_id: int, proc_count: int, fanout: int):
self._id = proc_id
self._proc_count = proc_count
self._peers = [id for id in range(0, self._proc_count) if id != self._id]
self._fanout = fanout
self._info = None
def on_local_message(self, msg: Message, ctx: Context):
if msg.type == 'START':
ctx.set_timer("gossip", 1)
elif msg.type == 'BROADCAST':
self.got_info(msg['info'], ctx)
def on_start(self, ctx: Context):
pass
def on_message(self, msg: Message, sender: str, ctx: Context):
if msg.type == 'GOSSIP_REQ' and self._info is not None:
ctx.send(Message('GOSSIP_RESP', {'info': self._info}), sender)
elif msg.type == 'GOSSIP_RESP' and self._info is None:
self.got_info(msg['info'], ctx)
def on_timer(self, timer_name: str, ctx: Context):
if self._info is None:
self.gossip(ctx)
ctx.set_timer("gossip", 1)
def got_info(self, info, ctx):
self._info = info
ctx.send_local(Message('DELIVER', {'info': self._info}))
ctx.cancel_timer("gossip")
ctx.send_local(Message('STOPPED', {}))
def gossip(self, ctx):
for peer in random.sample(self._peers, self._fanout):
ctx.send(Message('GOSSIP_REQ', {}), str(peer))
@@ -0,0 +1,38 @@
import random
from anysystem import Context, Message, Process
class Peer(Process):
def __init__(self, proc_id: int, proc_count: int, fanout: int):
self._id = proc_id
self._proc_count = proc_count
self._peers = [id for id in range(0, self._proc_count) if id != self._id]
self._fanout = fanout
self._info = None
def on_local_message(self, msg: Message, ctx: Context):
if msg.type == 'START':
ctx.set_timer("gossip", 1)
elif msg.type == 'BROADCAST':
self.got_info(msg['info'], ctx)
def on_start(self, ctx: Context):
pass
def on_message(self, msg: Message, sender: str, ctx: Context):
if msg.type == 'GOSSIP' and self._info is None:
self.got_info(msg['info'], ctx)
def on_timer(self, timer_name: str, ctx: Context):
if self._info is not None:
self.gossip(ctx)
ctx.set_timer("gossip", 1)
def got_info(self, info, ctx):
self._info = info
ctx.send_local(Message('DELIVER', {'info': self._info}))
def gossip(self, ctx):
for peer in random.sample(self._peers, self._fanout):
ctx.send(Message('GOSSIP', {'info': self._info}), str(peer))
@@ -0,0 +1,43 @@
import random
from anysystem import Context, Message, Process
class Peer(Process):
def __init__(self, proc_id: int, proc_count: int, fanout: int):
self._id = proc_id
self._proc_count = proc_count
self._peers = [id for id in range(0, self._proc_count) if id != self._id]
self._fanout = fanout
self._info = None
def on_local_message(self, msg: Message, ctx: Context):
if msg.type == 'START':
ctx.set_timer("gossip", 1)
elif msg.type == 'BROADCAST':
self.got_info(msg['info'], ctx)
def on_start(self, ctx: Context):
pass
def on_message(self, msg: Message, sender: str, ctx: Context):
if msg.type == 'GOSSIP_REQ':
if self._info is not None:
ctx.send(Message('GOSSIP_RESP', {'info': self._info}), sender)
else:
if msg['info'] is not None:
self.got_info(msg['info'], ctx)
elif msg.type == 'GOSSIP_RESP' and self._info is None:
self.got_info(msg['info'], ctx)
def on_timer(self, timer_name: str, ctx: Context):
self.gossip(ctx)
ctx.set_timer("gossip", 1)
def got_info(self, info, ctx):
self._info = info
ctx.send_local(Message('DELIVER', {'info': self._info}))
def gossip(self, ctx):
for peer in random.sample(self._peers, self._fanout):
ctx.send(Message('GOSSIP_REQ', {'info': self._info}), str(peer))
@@ -0,0 +1,54 @@
import random
from anysystem import Context, Message, Process
class Peer(Process):
def __init__(self, proc_id: int, proc_count: int, fanout: int):
self._id = proc_id
self._proc_count = proc_count
self._peers = [id for id in range(0, self._proc_count) if id != self._id]
self._fanout = fanout
self._info = None
self._stop_prob = 0.8
self._stopped = False
def on_local_message(self, msg: Message, ctx: Context):
if msg.type == 'START':
ctx.set_timer("gossip", 1)
elif msg.type == 'BROADCAST':
self.got_info(msg['info'], ctx)
def on_start(self, ctx: Context):
pass
def on_message(self, msg: Message, sender: str, ctx: Context):
if msg.type == 'GOSSIP_REQ':
if self._info is not None:
ctx.send(Message('GOSSIP_RESP', {'info': self._info}), sender)
elif msg['info'] is not None:
self.got_info(msg['info'], ctx)
elif msg.type == 'GOSSIP_RESP':
self.got_info(msg['info'], ctx)
def on_timer(self, timer_name: str, ctx: Context):
self.gossip(ctx)
ctx.set_timer("gossip", 1)
def got_info(self, info, ctx):
if self._info is None:
self._info = info
ctx.send_local(Message('DELIVER', {'info': self._info}))
else:
self.try_stop(ctx)
def gossip(self, ctx):
for peer in random.sample(self._peers, self._fanout):
ctx.send(Message('GOSSIP_REQ', {'info': self._info}), str(peer))
def try_stop(self, ctx):
if not self._stopped and random.uniform(0, 1) < self._stop_prob:
self._stopped = True
# Stop initiating exchanges; keep answering incoming requests.
ctx.cancel_timer("gossip")
ctx.send_local(Message('STOPPED', {}))
@@ -0,0 +1,129 @@
# Знакомство с gossip
В этой работе сравним push, pull и push-pull в симуляторе на [AnySystem](https://github.com/systems-group/anysystem). Перед каждым экспериментом запишите прогноз, затем сравните его с результатом и объясните различия.
## Подготовка
Откройте корень репозитория курса в IDE. Для VS Code с Pylance [стаб AnySystem](../../../../typings/anysystem/__init__.pyi) подключается через [настройки проекта](../../../../pyrightconfig.json). Для другой IDE укажите папку `typings` в корне репозитория как каталог стабов. Стаб содержит объявления и документацию API; реализация доступна в [исходнике AnySystem 0.3.0](https://github.com/osukhoroslov/anysystem/blob/v0.3.0/python/anysystem.py).
При запуске симулятора локально или в Docker Python-модуль встроен в AnySystem. Отдельный `anysystem.py` и настройка `PYTHONPATH` не нужны.
Из корня репозитория перейдите в каталог лабораторной:
```sh
cd materials/04-group-comm/seminar/gossip
```
Если установлен Rust, соберите текущую версию симулятора:
```sh
cargo install --locked --path simulator
distsys-gossip -h
```
Для запуска в Docker соберите образ из текущего кода:
```sh
docker build -t distsys-gossip-seminar simulator
docker run --rm -t -v ./:/impl distsys-gossip-seminar -h
```
Далее заменяйте `distsys-gossip` на `docker run --rm -t -v ./:/impl distsys-gossip-seminar`. Команды выполняются из каталога `gossip`. Если оболочка не передаёт относительный путь монтирования, в PowerShell используйте `--mount "type=bind,source=$($PWD.Path),target=/impl"` вместо `-v ./:/impl`.
## Модель и метрики
Распространяется одно сообщение, изначально известное процессу `0`. Все узлы знают состав группы и выбирают случайных соседей, исключая себя. Сетевая задержка равна 0,1 единицы модельного времени, периодический таймер срабатывает через 1. В этих экспериментах узлы не отказывают; можно задавать вероятность потери сетевого сообщения.
Основные параметры:
| Параметр | Значение |
| --- | --- |
| `-i` | Файл реализации |
| `-n` | Число узлов, не меньше 2 |
| `-f` | Число соседей для одного обмена, от 1 до `n - 1` |
| `-d` | Вероятность потери сообщения, от 0 до 1 |
| `-s` | Seed для воспроизведения запуска |
| `-t` | Положительный лимит модельного времени |
| `-q` | Завершить симуляцию при обнаружении полного охвата |
В таблице вывода `time` — модельное время, `delivered` — число узлов, доставивших сообщение приложению, `stopped` — число узлов, прекративших периодические инициативные обмены, `messages` — накопленное число сетевых отправлений, включая запросы и ответы. `STOPPED` не означает выключение узла: он сохраняет информацию и может отвечать на входящие запросы.
Симулятор выводит статистику после продвижения времени интервалами по 1. В итоговой строке `First full-coverage sample` указаны время первой напечатанной строки с `delivered = n` и число отправлений к ней. Обозначим их `t_all` и `M_all`. Это показатели с дискретностью вывода, а не точные мгновение последней доставки и число отправлений в это мгновение.
Строка `Finished` объясняет завершение:
- `full coverage (--quick-mode)` — сработало условие `-q`;
- `no pending events` — в симуляции не осталось событий;
- `time limit reached` — достигнут лимит времени.
Если условия совпали, отсутствие событий проверяется первым, затем полный охват с `-q`, затем лимит. Строка `Final state` всегда показывает фактический охват и трафик к завершению. Если полного охвата не было, выводится `Full coverage: not reached`. Завершение по лимиту не означает, что алгоритм сам остановился.
## 1. Сравнение push, pull и push-pull
Изучите [push.py](push.py), [pull.py](pull.py) и [push_pull.py](push_pull.py). В push информированный узел посылает данные соседям. В pull неинформированный узел запрашивает их; после получения перестаёт инициировать запросы, но продолжает отвечать другим. В push-pull запрос сам может нести информацию, а информированный получатель отвечает своими данными.
До запуска ответьте:
1. Какой подход быстрее распространит информацию в начале, когда источник один?
2. Какой подход быстрее найдёт последних неинформированных участников?
3. Должен ли самый быстрый вариант отправить меньше всего сообщений?
Запустите алгоритмы с одинаковыми параметрами:
```sh
distsys-gossip -i push.py -n 1000 -f 2 -s 123 -d 0 -t 60 -q
distsys-gossip -i pull.py -n 1000 -f 2 -s 123 -d 0 -t 60 -q
distsys-gossip -i push_pull.py -n 1000 -f 2 -s 123 -d 0 -t 60 -q
```
Заполните таблицу. Если полного охвата нет, укажите достигнутый охват и число отправлений к лимиту вместо `t_all` и `M_all`.
| Алгоритм | `n` | `f` | `d` | seed | `t_all` | `M_all` | Причина завершения |
| --- | --- | --- | --- | --- | --- | --- | --- |
| push | 1000 | 2 | 0 | 123 | | | |
| pull | 1000 | 2 | 0 | 123 | | | |
| push-pull | 1000 | 2 | 0 | 123 | | | |
Сравните начальный рост и доставку последним узлам. Объясните результат через действия отправителей. Флаг `-q` использует глобальную статистику симулятора: сами процессы не узнают, что сообщение уже получили все. В push и push-pull периодические обмены без этого ограничения продолжаются.
## 2. Изменение одного параметра
Выберите один алгоритм и сравните каждый следующий запуск с его базовым результатом. В командах замените `push.py` на выбранный файл.
Сначала увеличьте fanout с 2 до 4:
```sh
distsys-gossip -i push.py -n 1000 -f 4 -s 123 -d 0 -t 60 -q
```
Затем верните fanout 2 и добавьте вероятность потери 0,2:
```sh
distsys-gossip -i push.py -n 1000 -f 2 -s 123 -d 0.2 -t 60 -q
```
Для каждого запуска сначала запишите прогноз, затем добавьте строку результатов в таблицу. Как изменились время и цена охвата? Почему больше контактов за раунд не обязательно означает пропорционально меньшую задержку? Что можно заключить, если охват не достигнут за 60 единиц времени?
Один запуск показывает конкретное выполнение. При наличии времени повторите сравнение на seed `124` и `125`. Одинаковый seed воспроизводит запуск данной реализации; разные алгоритмы расходуют случайные числа по-разному, поэтому их контакты и потери не обязаны совпадать. Если меняете лимит времени, используйте один лимит для сопоставляемых запусков.
## 3. Полный охват и прекращение обменов
Изучите [push_pull_stop.py](push_pull_stop.py) и запустите его без `-q`:
```sh
distsys-gossip -i push_pull_stop.py -n 1000 -f 2 -s 123 -d 0 -t 60
```
При повторном получении информации через `got_info` узел с вероятностью 0,8 отменяет свой таймер и выдаёт `STOPPED`. Он продолжает отвечать на запросы. Неинформированные узлы продолжают искать данные; остановка таймера у держателя не удаляет доступную копию.
Выпишите показатели первой строки с полным охватом и показатели завершения. Совпадают ли они? Какой трафик возникает между ними? Подтвердите по `Finished`, что запуск завершился из-за отсутствия событий, а не по лимиту.
Для сравнения стоимости распространения используйте `M_all` всех четырёх вариантов. Число отправлений к концу запуска с остановкой — другая метрика. Итоговые `max/min/mean` отправлений по узлам также относятся к концу запуска, который может наступить позже полного охвата.
## Обсуждение
- Какие оптимизации могут уменьшить число повторных передач?
- Почему успешный запуск при потерях не доказывает Uniform Agreement при отказах процессов?
- Какая дополнительная информация и логика понадобятся для нескольких сообщений и причинного порядка доставки?
Сформулируйте вывод о скорости распространения, сетевых затратах и прекращении активности отдельно. Свяжите каждый вывод с наблюдением из таблицы и предположениями модели.
@@ -0,0 +1,5 @@
target
**/target
**/__pycache__
**/.pytest_cache
*.pyc
@@ -0,0 +1,960 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anysystem"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01badd3bc0d042e4399f8f685c394a79a72e7f233b3d3b5eabb3b94734f4dbe"
dependencies = [
"colored",
"downcast-rs",
"dyn-clone",
"indexmap 2.14.0",
"lazy_static",
"log",
"ordered-float",
"pyo3",
"rand",
"rand_pcg",
"regex",
"rstest",
"serde",
"serde_json",
"simcore",
"sugars",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "clap"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap 1.9.3",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
dependencies = [
"heck 0.4.1",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "colored"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.59.0",
]
[[package]]
name = "distsys-gossip"
version = "0.1.0"
dependencies = [
"anysystem",
"clap",
"serde",
"serde_json",
"sugars",
]
[[package]]
name = "downcast-rs"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ordered-float"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
dependencies = [
"num-traits",
"rand",
"serde",
]
[[package]]
name = "os_str_bytes"
version = "6.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "portable-atomic"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro-crate"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pyo3"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
dependencies = [
"libc",
"once_cell",
"portable-atomic",
"pyo3-build-config",
"pyo3-ffi",
"pyo3-macros",
]
[[package]]
name = "pyo3-build-config"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
dependencies = [
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
dependencies = [
"libc",
"pyo3-build-config",
]
[[package]]
name = "pyo3-macros"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn 2.0.106",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"serde",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
"serde",
]
[[package]]
name = "rand_pcg"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
dependencies = [
"rand_core",
]
[[package]]
name = "regex"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "relative-path"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "rstest"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682"
dependencies = [
"futures",
"futures-timer",
"rstest_macros",
"rustc_version",
]
[[package]]
name = "rstest_macros"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d"
dependencies = [
"cfg-if",
"glob",
"proc-macro-crate",
"proc-macro2",
"quote",
"regex",
"relative-path",
"rustc_version",
"syn 2.0.106",
"unicode-ident",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "semver"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "serde_json"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
dependencies = [
"indexmap 2.14.0",
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_type_name"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92acc4cd6ae20767c54d6cf1a075624e7f4d9e99d7ebc685398ff243144d8714"
dependencies = [
"serde",
]
[[package]]
name = "simcore"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20dfec85e94e569fa5b810b2d992c68b361f31bfba471447427536488c9c292"
dependencies = [
"colored",
"downcast-rs",
"dyn-clone",
"erased-serde",
"futures",
"log",
"rand",
"rand_pcg",
"rustc-hash",
"serde",
"serde_json",
"serde_type_name",
]
[[package]]
name = "slab"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "sugars"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc0db74f9ee706e039d031a560bd7d110c7022f016051b3d33eeff9583e3e67a"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "target-lexicon"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap 2.14.0",
"toml_datetime",
"winnow",
]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
]
[[package]]
name = "zerocopy"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
@@ -0,0 +1,12 @@
[package]
name = "distsys-gossip"
version = "0.1.0"
edition = "2021"
resolver = "3"
[dependencies]
anysystem = "=0.3.0"
clap = { version = "3.1.17", features = ["cargo", "derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sugars = "3"
@@ -0,0 +1,19 @@
# syntax=docker/dockerfile:1
FROM rust:1.97.1-bookworm@sha256:0e2bcaef56d041a486784e54104a81aebe0da44bd03019bd70bc0401e42e4a97 AS builder
RUN apt-get update && apt-get install -y build-essential python3-dev
WORKDIR /simulator
COPY . .
RUN --mount=type=cache,id=distsys-course-cargo-registry,target=/usr/local/cargo/registry,sharing=locked \
--mount=type=cache,id=distsys-course-cargo-target-rust-1-97-1,target=/simulator/target,sharing=locked \
cargo install --locked --path . --root /opt/course
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y python3-dev && rm -rf /var/lib/apt/lists/*
COPY --from=builder /opt/course/bin/distsys-gossip /usr/local/bin/distsys-gossip
WORKDIR /impl
ENTRYPOINT ["distsys-gossip"]
@@ -0,0 +1,204 @@
use std::env;
use clap::{CommandFactory, ErrorKind, Parser};
use serde::Serialize;
use sugars::boxed;
use anysystem::python::PyProcessFactory;
use anysystem::{Message, System};
// CLI -----------------------------------------------------------------------------------------------------------------
/// Gossip simulator.
#[derive(Parser, Debug)]
#[clap(about, long_about = None)]
struct Args {
/// Path to Python file with process implementations.
#[clap(long = "impl", short)]
impl_path: String,
/// Number of nodes.
#[clap(long, short, default_value = "10")]
nodes: u32,
/// Network drop rate.
#[clap(long, short, default_value = "0")]
drop_rate: f64,
/// Fan-out (how many peers to contact on each round).
#[clap(long, short, default_value = "1")]
fanout: u32,
/// Stop simulation when all nodes delivered info.
#[clap(long, short)]
quick_mode: bool,
/// Time limit in simulation.
#[clap(long, short, default_value = "60")]
time_limit: u32,
/// Random seed.
#[clap(long, short, default_value = "123")]
seed: u64,
}
impl Args {
fn validate(&self) -> Result<(), &'static str> {
if self.nodes < 2 {
return Err("--nodes must be at least 2");
}
if self.fanout == 0 || self.fanout >= self.nodes {
return Err("--fanout must be between 1 and nodes - 1");
}
if !(0.0..=1.0).contains(&self.drop_rate) {
return Err("--drop-rate must be a finite number between 0 and 1");
}
if self.time_limit == 0 {
return Err("--time-limit must be positive");
}
Ok(())
}
}
// MAIN ----------------------------------------------------------------------------------------------------------------
fn main() {
let args = Args::parse();
if let Err(message) = args.validate() {
Args::command()
.error(ErrorKind::ValueValidation, message)
.exit();
}
env::set_var("PYTHONHASHSEED", args.seed.to_string());
let proc_factory = PyProcessFactory::new(&args.impl_path, "Peer");
println!("Nodes: {}", args.nodes);
println!("Fanout: {}", args.fanout);
println!("Network drop rate: {}", args.drop_rate);
println!("Implementation: {}", args.impl_path);
println!("Seed: {}", args.seed);
println!("Time limit: {}", args.time_limit);
println!("Quick mode: {}", args.quick_mode);
let mut sys = build_system(
proc_factory,
args.nodes,
args.drop_rate,
args.fanout,
args.seed,
);
sys.send_local_message(
"0",
Message::json(
"BROADCAST",
&BroadcastMessage {
info: "Some very important information to propagate to all nodes",
},
),
);
println!(
"\n{:<10} {:<12} {:<12} {:<12}",
"time", "delivered", "stopped", "messages"
);
let mut first_full_coverage = None;
let (reason, delivered, stopped) = loop {
let more_events = sys.step_for_duration(1.);
let (delivered, stopped) = get_stats(&sys);
println!(
"{:<10} {:<12} {:<12} {:<12}",
sys.time(),
delivered,
stopped,
sys.network().network_message_count()
);
// Capture the first printed full-coverage sample, before any later traffic.
if delivered == args.nodes && first_full_coverage.is_none() {
first_full_coverage = Some((sys.time(), sys.network().network_message_count()));
}
if !more_events {
break ("no pending events", delivered, stopped);
}
if args.quick_mode && delivered == args.nodes {
break ("full coverage (--quick-mode)", delivered, stopped);
}
if sys.time() >= args.time_limit as f64 {
break ("time limit reached", delivered, stopped);
}
};
println!("\nFinished: {}", reason);
println!(
"Final state: time={}, delivered={}/{}, stopped={}, messages={}",
sys.time(),
delivered,
args.nodes,
stopped,
sys.network().network_message_count()
);
match first_full_coverage {
Some((time, messages)) => println!(
"First full-coverage sample: time={}, messages={} (sampled every 1 time unit)",
time, messages
),
None => println!("Full coverage: not reached"),
}
let sent_counts: Vec<u64> = sys
.process_names()
.iter()
.map(|p| sys.sent_message_count(p))
.collect();
println!(
"\nMessages sent by each node: max={}, min={}, mean={:.2}",
sent_counts.iter().max().unwrap(),
sent_counts.iter().min().unwrap(),
sent_counts.iter().sum::<u64>() as f64 / sent_counts.len() as f64
)
}
// UTILS ---------------------------------------------------------------------------------------------------------------
#[derive(Serialize)]
struct StartMessage {}
#[derive(Serialize)]
struct BroadcastMessage<'a> {
info: &'a str,
}
fn build_system(
proc_factory: PyProcessFactory,
nodes: u32,
drop_rate: f64,
fanout: u32,
seed: u64,
) -> System {
let mut sys = System::new(seed);
sys.network().set_delay(0.1);
sys.network().set_drop_rate(drop_rate);
for proc_id in 0..nodes {
// process and node on which it runs have the same name
let name = proc_id.to_string();
sys.add_node(&name);
let proc = proc_factory.build((proc_id, nodes, fanout), seed);
sys.add_process(&name, boxed!(proc), &name);
sys.send_local_message(&name, Message::json("START", &StartMessage {}))
}
sys
}
fn get_stats(sys: &System) -> (u32, u32) {
let mut delivered_count = 0;
let mut stopped_count = 0;
for proc in sys.process_names() {
let outbox = sys.local_outbox(&proc);
let msg_count = outbox.len();
if msg_count > 0 {
assert_eq!(outbox.first().unwrap().tip, "DELIVER");
delivered_count += 1;
}
if msg_count == 2 {
assert_eq!(outbox.get(1).unwrap().tip, "STOPPED");
stopped_count += 1;
}
assert!(msg_count <= 2);
}
(delivered_count, stopped_count)
}