mirror of
https://github.com/logos-blockchain/logos-blockchain-simulations.git
synced 2026-01-15 03:23:12 +00:00
Targeted experiments for queuing mechanism gather series into dataframe put exp_id to the CSV path revert iterations back to num_nodes/2 add missing print and decrease msg_interval_sec change param sequence for readability use struct instead of pickle for fixed-size & faster serde include dtime series into dataframe optimize: choose optimized connection type according to latency setting add skip_sending_noise option optimize filling up the queue with noises move queue_type to the end of param set, and build CSV gradually row by row fix: consider num_senders when waiting until all messages are disseminated fix: sample senders without duplicate fix: build param combinations correctly add plot script initialize MinSizeMixQueue with noises define SessionParameterSet and add paramset for session2 improve topology connectivity check to avoid "maxmimum recursions depth exceeded" error fix: the correct parameter set constructor store individual series to separate CSV files reorganize files and draw plot automatically start series file id from 1 (not 0) add queue_type CLI argument for parallelization pretty format of elapsed time pretty format of elapsed time add merge CLI and draw multiple plots split functions do not draw plot for each session use concurrent.futures to utilize multiprocessing add from_paramset argument fix: count num of finished iterations correctly draw plots for num_sent_msgs and num_senders for specific experiments
160 lines
5.6 KiB
Python
160 lines
5.6 KiB
Python
import math
|
|
from abc import abstractmethod
|
|
from collections import Counter
|
|
from typing import Awaitable
|
|
|
|
import pandas
|
|
from typing_extensions import override
|
|
|
|
from framework import Framework, Queue
|
|
from protocol.connection import SimplexConnection
|
|
from sim.config import LatencyConfig, NetworkConfig
|
|
from sim.state import NodeState
|
|
|
|
|
|
class RemoteSimplexConnection(SimplexConnection):
|
|
"""
|
|
A simplex connection implementation that simulates network latency.
|
|
"""
|
|
|
|
def __init__(self, config: LatencyConfig, framework: Framework):
|
|
self.framework = framework
|
|
# A connection has a random constant latency
|
|
self.latency = config.random_latency()
|
|
# A queue of tuple(timestamp, msg) where a sender puts messages to be sent
|
|
self.send_queue: Queue[tuple[float, bytes]] = framework.queue()
|
|
# A task that reads messages from send_queue, and puts them to recv_queue.
|
|
# Before putting messages to recv_queue, the task simulates network latency according to the timestamp of each message.
|
|
self.relayer = framework.spawn(self.__run_relayer())
|
|
# A queue where a receiver gets messages
|
|
self.recv_queue: Queue[bytes] = framework.queue()
|
|
|
|
async def send(self, data: bytes) -> None:
|
|
await self.send_queue.put((self.framework.now(), data))
|
|
self.on_sending(data)
|
|
|
|
async def recv(self) -> bytes:
|
|
return await self.recv_queue.get()
|
|
|
|
async def __run_relayer(self):
|
|
"""
|
|
A task that reads messages from send_queue, and puts them to recv_queue.
|
|
Before putting messages to recv_queue, the task simulates network latency according to the timestamp of each message.
|
|
"""
|
|
while True:
|
|
sent_time, data = await self.send_queue.get()
|
|
# Simulate network latency
|
|
delay = self.latency - (self.framework.now() - sent_time)
|
|
if delay > 0:
|
|
await self.framework.sleep(delay)
|
|
|
|
# Relay msg to the recv_queue.
|
|
# Call on_receiving (e.g. for updating stats) before msg is read from recv_queue by the receiver
|
|
# because the time at which enters the node is important when viewed from the outside.
|
|
self.on_receiving(data)
|
|
await self.recv_queue.put(data)
|
|
|
|
def on_sending(self, data: bytes) -> None:
|
|
# Should be overridden by subclass
|
|
pass
|
|
|
|
def on_receiving(self, data: bytes) -> None:
|
|
# Should be overridden by subclass
|
|
pass
|
|
|
|
|
|
class MeteredRemoteSimplexConnection(RemoteSimplexConnection):
|
|
"""
|
|
An extension of RemoteSimplexConnection that measures bandwidth usages.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: LatencyConfig,
|
|
framework: Framework,
|
|
meter_start_time: float,
|
|
):
|
|
super().__init__(config, framework)
|
|
# To measure bandwidth usages
|
|
self.meter_start_time = meter_start_time
|
|
self.send_meters: list[int] = []
|
|
self.recv_meters: list[int] = []
|
|
|
|
@override
|
|
def on_sending(self, data: bytes) -> None:
|
|
"""
|
|
Update statistics when sending a message
|
|
"""
|
|
self.__update_meter(self.send_meters, len(data))
|
|
|
|
@override
|
|
def on_receiving(self, data: bytes) -> None:
|
|
"""
|
|
Update statistics when receiving a message
|
|
"""
|
|
self.__update_meter(self.recv_meters, len(data))
|
|
|
|
def __update_meter(self, meters: list[int], size: int):
|
|
"""
|
|
Accumulates the bandwidth usage in the current time slot (seconds).
|
|
"""
|
|
slot = math.floor(self.framework.now() - self.meter_start_time)
|
|
assert slot >= len(meters) - 1
|
|
# Fill zeros for the empty time slots
|
|
meters.extend([0] * (slot - len(meters) + 1))
|
|
meters[-1] += size
|
|
|
|
def sending_bandwidths(self) -> pandas.Series:
|
|
"""
|
|
Returns the accumulated sending bandwidth usage over time
|
|
"""
|
|
return self.__bandwidths(self.send_meters)
|
|
|
|
def receiving_bandwidths(self) -> pandas.Series:
|
|
"""
|
|
Returns the accumulated receiving bandwidth usage over time
|
|
"""
|
|
return self.__bandwidths(self.recv_meters)
|
|
|
|
def __bandwidths(self, meters: list[int]) -> pandas.Series:
|
|
return pandas.Series(meters, name="bandwidth")
|
|
|
|
|
|
class ObservedMeteredRemoteSimplexConnection(MeteredRemoteSimplexConnection):
|
|
"""
|
|
An extension of MeteredRemoteSimplexConnection that is observed by passive observer.
|
|
The observer monitors the node states of the sender and receiver and message sizes.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: LatencyConfig,
|
|
framework: Framework,
|
|
meter_start_time: float,
|
|
send_node_states: list[NodeState],
|
|
recv_node_states: list[NodeState],
|
|
):
|
|
super().__init__(config, framework, meter_start_time)
|
|
|
|
# To measure node states over time
|
|
self.send_node_states = send_node_states
|
|
self.recv_node_states = recv_node_states
|
|
# To measure the size of messages sent via this connection
|
|
self.msg_sizes: Counter[int] = Counter()
|
|
|
|
@override
|
|
def on_sending(self, data: bytes) -> None:
|
|
super().on_sending(data)
|
|
self.__update_node_state(self.send_node_states, NodeState.SENDING)
|
|
self.msg_sizes.update([len(data)])
|
|
|
|
@override
|
|
def on_receiving(self, data: bytes) -> None:
|
|
super().on_receiving(data)
|
|
self.__update_node_state(self.recv_node_states, NodeState.RECEIVING)
|
|
|
|
def __update_node_state(self, node_states: list[NodeState], state: NodeState):
|
|
# The time unit of node states is milliseconds
|
|
ms = math.floor(self.framework.now() * 1000)
|
|
node_states[ms] = state
|