mirror of
https://github.com/logos-blockchain/logos-blockchain-simulations.git
synced 2026-01-09 00:23:09 +00:00
245 lines
8.7 KiB
Python
245 lines
8.7 KiB
Python
from __future__ import annotations
|
|
|
|
import concurrent.futures
|
|
import os
|
|
import random
|
|
import time
|
|
import traceback
|
|
from copy import deepcopy
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import Counter
|
|
|
|
import pandas as pd
|
|
import usim
|
|
|
|
from protocol.nomssip import NomssipConfig
|
|
from protocol.temporalmix import TemporalMixConfig, TemporalMixType
|
|
from queuesim.config import Config
|
|
from queuesim.message import MESSAGE_SIZE
|
|
from queuesim.paramset import (
|
|
EXPERIMENT_TITLES,
|
|
ExperimentID,
|
|
ParameterSet,
|
|
SessionID,
|
|
build_parameter_sets,
|
|
)
|
|
from queuesim.simulation import Simulation
|
|
from queuesim.util import format_elapsed_time
|
|
from sim.config import LatencyConfig, TopologyConfig
|
|
|
|
DEFAULT_CONFIG = Config(
|
|
num_nodes=10,
|
|
nomssip=NomssipConfig(
|
|
peering_degree=3,
|
|
transmission_rate_per_sec=10,
|
|
msg_size=MESSAGE_SIZE,
|
|
temporal_mix=TemporalMixConfig(
|
|
mix_type=TemporalMixType.NONE,
|
|
min_queue_size=10,
|
|
seed_generator=random.Random(0),
|
|
),
|
|
skip_sending_noise=True,
|
|
),
|
|
topology=TopologyConfig(
|
|
seed=random.Random(0),
|
|
),
|
|
latency=LatencyConfig(
|
|
min_latency_sec=0,
|
|
max_latency_sec=0,
|
|
seed=random.Random(0),
|
|
),
|
|
num_sent_msgs=1,
|
|
msg_interval_sec=0.1,
|
|
num_senders=1,
|
|
sender_generator=random.Random(0),
|
|
)
|
|
|
|
PARAMSET_INFO_COLUMNS = [
|
|
"paramset",
|
|
"num_nodes",
|
|
"peering_degree",
|
|
"min_queue_size",
|
|
"transmission_rate",
|
|
"num_sent_msgs",
|
|
"num_senders",
|
|
"queue_type",
|
|
"num_iterations",
|
|
]
|
|
|
|
|
|
def run_session(
|
|
exp_id: ExperimentID,
|
|
session_id: SessionID,
|
|
queue_type: TemporalMixType,
|
|
num_workers: int,
|
|
outdir: str,
|
|
from_paramset: int = 1,
|
|
):
|
|
print("******************************************************************")
|
|
print(f"{exp_id.name}: {session_id.name}: {EXPERIMENT_TITLES[exp_id]}")
|
|
print(f"Queue type: {queue_type.name}")
|
|
print("******************************************************************")
|
|
|
|
# Create a directory and initialize a CSV file only with a header
|
|
assert os.path.isdir(outdir)
|
|
subdir = f"__WIP__queuesim_e{exp_id.value}s{session_id.value}_{queue_type.name}_{datetime.now().isoformat()}___DUR__"
|
|
os.makedirs(f"{outdir}/{subdir}")
|
|
|
|
# Prepare all parameter sets of the session
|
|
paramsets = build_parameter_sets(exp_id, session_id, queue_type)
|
|
|
|
# Run the simulations for each parameter set, using multi processes
|
|
session_start_time = time.time()
|
|
future_map: dict[concurrent.futures.Future[tuple[bool, float]], IterationInfo] = (
|
|
dict()
|
|
)
|
|
with concurrent.futures.ProcessPoolExecutor(max_workers=num_workers) as executor:
|
|
# Submit all iterations of all parameter sets to the ProcessPoolExecutor
|
|
for paramset in paramsets:
|
|
if paramset.id < from_paramset:
|
|
continue
|
|
paramset_dir = f"{outdir}/{subdir}/__WIP__paramset_{paramset.id}"
|
|
os.makedirs(paramset_dir)
|
|
__save_paramset_info(paramset, f"{paramset_dir}/paramset.csv")
|
|
future_map.update(_submit_iterations(paramset, executor, paramset_dir))
|
|
|
|
# Wait until all parameter sets are done
|
|
iterations_done: Counter[int] = Counter() # per paramset_id
|
|
paramsets_done: set[int] = set()
|
|
for future in concurrent.futures.as_completed(future_map):
|
|
iter = future_map[future]
|
|
succeeded, _ = future.result()
|
|
if not succeeded:
|
|
print("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
|
|
print("ITERATION FAILED: See the err file")
|
|
print(iter)
|
|
print("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
|
|
else:
|
|
# Rename the WIP iteration result file to the final name
|
|
new_iteration_csv_path = iter.out_csv_path.replace(
|
|
"__WIP__iteration_", "iteration_"
|
|
)
|
|
assert not os.path.exists(new_iteration_csv_path)
|
|
os.rename(iter.out_csv_path, new_iteration_csv_path)
|
|
|
|
iterations_done.update([iter.paramset.id])
|
|
|
|
# If all iterations of the paramset are done, print a log
|
|
if iterations_done[iter.paramset.id] == iter.paramset.num_iterations:
|
|
paramsets_done.add(iter.paramset.id)
|
|
paramset_dir = f"{outdir}/{subdir}/__WIP__paramset_{iter.paramset.id}"
|
|
new_paramset_dir = f"{outdir}/{subdir}/paramset_{iter.paramset.id}"
|
|
assert not os.path.exists(new_paramset_dir)
|
|
os.rename(paramset_dir, new_paramset_dir)
|
|
print("================================================")
|
|
print(
|
|
f"ParamSet-{iter.paramset.id} is done. Total {len(paramsets_done)+(from_paramset-1)}/{len(paramsets)} paramsets have been done so far."
|
|
)
|
|
print(f"Renamed the WIP directory to {new_paramset_dir}")
|
|
print("================================================")
|
|
|
|
session_elapsed_time = time.time() - session_start_time
|
|
session_elapsed_time_str = format_elapsed_time(session_elapsed_time)
|
|
|
|
# Rename the WIP directory to the final name
|
|
new_subdir = subdir.replace("__WIP__", "").replace(
|
|
"__DUR__", session_elapsed_time_str
|
|
)
|
|
assert not os.path.exists(f"{outdir}/{new_subdir}")
|
|
os.rename(f"{outdir}/{subdir}", f"{outdir}/{new_subdir}")
|
|
|
|
print("******************************************************************")
|
|
print(f"Session Elapsed Time: {session_elapsed_time_str}")
|
|
print(f"Renamed the WIP directory to {outdir}/{new_subdir}")
|
|
print("******************************************************************")
|
|
|
|
|
|
def __save_paramset_info(paramset: ParameterSet, path: str):
|
|
assert not os.path.exists(path)
|
|
info = {
|
|
"paramset": paramset.id,
|
|
"num_nodes": paramset.num_nodes,
|
|
"peering_degree": paramset.peering_degree,
|
|
"min_queue_size": paramset.min_queue_size,
|
|
"transmission_rate": paramset.transmission_rate,
|
|
"num_sent_msgs": paramset.num_sent_msgs,
|
|
"num_senders": paramset.num_senders,
|
|
"queue_type": paramset.queue_type.name,
|
|
"num_iterations": paramset.num_iterations,
|
|
}
|
|
assert info.keys() == set(PARAMSET_INFO_COLUMNS)
|
|
pd.DataFrame([info]).to_csv(path, mode="w", header=True, index=False)
|
|
|
|
|
|
def _submit_iterations(
|
|
paramset: ParameterSet,
|
|
executor: concurrent.futures.ProcessPoolExecutor,
|
|
outdir: str,
|
|
) -> dict[concurrent.futures.Future[tuple[bool, float]], IterationInfo]:
|
|
"""
|
|
Submit all iterations of the given parameter set to the executor,
|
|
so that they can be ran by the ProcessPoolExecutor.
|
|
"""
|
|
assert os.path.exists(outdir)
|
|
|
|
# Prepare the configuration for the parameter set
|
|
cfg = deepcopy(DEFAULT_CONFIG)
|
|
paramset.apply_to(cfg)
|
|
|
|
print(
|
|
f"Scheduling {paramset.num_iterations} iterations for the paramset:{paramset.id}"
|
|
)
|
|
|
|
future_map: dict[concurrent.futures.Future[tuple[bool, float]], IterationInfo] = (
|
|
dict()
|
|
)
|
|
for i in range(paramset.num_iterations):
|
|
# Update seeds for the current iteration
|
|
# Deepcopy the cfg to avoid the same cfg instance between iteration jobs.
|
|
iter_cfg = deepcopy(cfg)
|
|
iter_cfg.nomssip.temporal_mix.seed_generator = random.Random(i)
|
|
iter_cfg.topology.seed = random.Random(i)
|
|
iter_cfg.latency.seed = random.Random(i)
|
|
iter_cfg.sender_generator = random.Random(i)
|
|
# Submit the iteration to the executor
|
|
out_csv_path = f"{outdir}/__WIP__iteration_{i}.csv"
|
|
err_path = f"{outdir}/iteration_{i}.err"
|
|
topology_path = f"{outdir}/iteration_{i}_topology.csv"
|
|
future = executor.submit(
|
|
_run_iteration, iter_cfg, out_csv_path, err_path, topology_path
|
|
)
|
|
future_map[future] = IterationInfo(
|
|
paramset, i, out_csv_path, err_path, topology_path
|
|
)
|
|
|
|
return future_map
|
|
|
|
|
|
def _run_iteration(
|
|
cfg: Config, out_csv_path: str, err_path: str, topology_path: str
|
|
) -> tuple[bool, float]:
|
|
"""
|
|
Run a single iteration of a certain parameter set.
|
|
The iteration uses the independent uSim instance.
|
|
Returns False if exception happened.
|
|
"""
|
|
start_time = time.time()
|
|
try:
|
|
sim = Simulation(cfg)
|
|
usim.run(sim.run(out_csv_path, topology_path))
|
|
return True, time.time() - start_time
|
|
except BaseException as e:
|
|
with open(err_path, "w") as f:
|
|
traceback.print_exc(file=f)
|
|
return False, time.time() - start_time
|
|
|
|
|
|
@dataclass
|
|
class IterationInfo:
|
|
paramset: ParameterSet
|
|
iteration_idx: int
|
|
out_csv_path: str
|
|
err_path: str
|
|
topology_path: str
|