Unverified Commit bdea721b authored by Jakob Görgen's avatar Jakob Görgen
Browse files

experiments/simbricks/orchestration: started impl draft for instantiation +...

experiments/simbricks/orchestration: started impl draft for instantiation + added example usage along network simulator
parent 342a58b8
...@@ -29,8 +29,6 @@ import simbricks.orchestration.simulation.host as sim_host ...@@ -29,8 +29,6 @@ import simbricks.orchestration.simulation.host as sim_host
import simbricks.orchestration.simulation.channel as sim_channel import simbricks.orchestration.simulation.channel as sim_channel
import simbricks.orchestration.simulation.pcidev as sim_pcidev import simbricks.orchestration.simulation.pcidev as sim_pcidev
import simbricks.orchestration.system.base as system_base import simbricks.orchestration.system.base as system_base
import simbricks.orchestration.system.host.base as system_host_base import simbricks.orchestration.system.host.base as system_host_base
import simbricks.orchestration.system.eth as system_eth import simbricks.orchestration.system.eth as system_eth
...@@ -40,7 +38,12 @@ import simbricks.orchestration.system.pcie as system_pcie ...@@ -40,7 +38,12 @@ import simbricks.orchestration.system.pcie as system_pcie
from simbricks.orchestration.proxy import NetProxyConnecter, NetProxyListener from simbricks.orchestration.proxy import NetProxyConnecter, NetProxyListener
from simbricks.orchestration.simulators import ( from simbricks.orchestration.simulators import (
HostSim, I40eMultiNIC, NetSim, NICSim, PCIDevSim, Simulator HostSim,
I40eMultiNIC,
NetSim,
NICSim,
PCIDevSim,
Simulator,
) )
...@@ -86,24 +89,25 @@ class Experiment(object): ...@@ -86,24 +89,25 @@ class Experiment(object):
self.sys_sim_map: dict[system.Component, simulation.Simulator] = {} self.sys_sim_map: dict[system.Component, simulation.Simulator] = {}
"""System component and its simulator pairs""" """System component and its simulator pairs"""
self._chan_map: dict[system.Channel, simulation.channel.Channel] = {} self._chan_map: dict[system_base.Channel, sim_channel.Channel] = {}
"""Channel spec and its instanciation""" """Channel spec and its instanciation"""
def add_spec_sim_map(self, sys: system.component, sim: simulation.Simulator): def add_spec_sim_map(self, sys: system.component, sim: simulation.Simulator):
""" Add a mapping from specification to simulation instance""" """Add a mapping from specification to simulation instance"""
if sys in sys_sim_map: if sys in sys_sim_map:
raise Exception("system component is already mapped by simulator") raise Exception("system component is already mapped by simulator")
self.sys_sim_map[sys] = sim self.sys_sim_map[sys] = sim
def is_channel_instantiated(self, chan: system.Channel) -> bool: def is_channel_instantiated(self, chan: system_base.Channel) -> bool:
return chan in self._chan_map return chan in self._chan_map
def retrieve_or_create_channel(self, chan: system.Channel) -> simulation.channel.Channel: def retrieve_or_create_channel(
self, chan: system_base.Channel
) -> sim_channel.Channel:
if self.is_channel_instantiated(chan): if self.is_channel_instantiated(chan):
return self._chan_map[chan] return self._chan_map[chan]
# TODO: pass in specification into channel constructor channel = sim_channel.Channel(self, chan)
channel = simulation.channel.Channel(self)
self._chan_map[chan] = channel self._chan_map[chan] = channel
return channel return channel
...@@ -115,7 +119,7 @@ class Experiment(object): ...@@ -115,7 +119,7 @@ class Experiment(object):
"""Add a host simulator to the experiment.""" """Add a host simulator to the experiment."""
for h in self.hosts: for h in self.hosts:
if h.name == sim.name: if h.name == sim.name:
raise ValueError('Duplicate host name') raise ValueError("Duplicate host name")
self.hosts.append(sim) self.hosts.append(sim)
def add_nic(self, sim: NICSim | I40eMultiNIC): def add_nic(self, sim: NICSim | I40eMultiNIC):
...@@ -126,26 +130,26 @@ class Experiment(object): ...@@ -126,26 +130,26 @@ class Experiment(object):
"""Add a PCIe device simulator to the experiment.""" """Add a PCIe device simulator to the experiment."""
for d in self.pcidevs: for d in self.pcidevs:
if d.name == sim.name: if d.name == sim.name:
raise ValueError('Duplicate pcidev name') raise ValueError("Duplicate pcidev name")
self.pcidevs.append(sim) self.pcidevs.append(sim)
def add_memdev(self, sim: simulators.MemDevSim): def add_memdev(self, sim: simulators.MemDevSim):
for d in self.memdevs: for d in self.memdevs:
if d.name == sim.name: if d.name == sim.name:
raise ValueError('Duplicate memdev name') raise ValueError("Duplicate memdev name")
self.memdevs.append(sim) self.memdevs.append(sim)
def add_netmem(self, sim: simulators.NetMemSim): def add_netmem(self, sim: simulators.NetMemSim):
for d in self.netmems: for d in self.netmems:
if d.name == sim.name: if d.name == sim.name:
raise ValueError('Duplicate netmems name') raise ValueError("Duplicate netmems name")
self.netmems.append(sim) self.netmems.append(sim)
def add_network(self, sim: NetSim) -> None: def add_network(self, sim: NetSim) -> None:
"""Add a network simulator to the experiment.""" """Add a network simulator to the experiment."""
for n in self.networks: for n in self.networks:
if n.name == sim.name: if n.name == sim.name:
raise ValueError('Duplicate net name') raise ValueError("Duplicate net name")
self.networks.append(sim) self.networks.append(sim)
def all_simulators(self) -> tp.Iterable[Simulator]: def all_simulators(self) -> tp.Iterable[Simulator]:
......
# Copyright 2022 Max Planck Institute for Software Systems, and
# National University of Singapore
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from simbricks.orchestration import simulation
import enum
import pathlib
class SockType(enum.Enum):
LISTEN = enum.auto()
CONNECT = enum.auto()
class Socket:
def __init__(self, path: str = "", ty: SockType = SockType.LISTEN):
self._path = path
self._type = ty
class InstantiationEnvironment:
def __init__(
self,
repo_path: str = pathlib.Path(__file__).parents[3].resolve(),
workdir: str = pathlib.Path(),
cpdir: str = pathlib.Path(),
):
# TODO: add more parameters that wont change during instantiation
self._repodir: str = pathlib.Path(repo_path).absolute()
self._workdir: str = pathlib.Path(workdir).absolute()
self._cpdir: str = pathlib.Path(cpdir).absolute()
class Instantiation:
def __init__(
self, simulation, env: InstantiationEnvironment = InstantiationEnvironment()
):
self._simulation = simulation
self._env: InstantiationEnvironment = env
self._socket_tracker: set[tuple[simulation.channel.Channel, SockType]] = set()
def get_socket_path(self, chan: simulation.channel.Channel) -> Socket:
# TODO: use self._socket_tracker to determine socket type that is needed
sock_type = SockType.LISTEN
# TODO: generate socket path
sock_path = ""
return Socket(sock_path, sock_type)
@staticmethod
def is_absolute_exists(path: str) -> bool:
path = pathlib.Path(path)
return path.is_absolute() and path.is_file()
# TODO: add more methods constructing paths as required by methods in simulators or image handling classes
def hd_path(self, hd_name_or_path: str) -> str:
if Instantiation.is_absolute_exists(hd_name_or_path):
return hd_name_or_path
path = pathlib.Path(
f"{self._env._repodir}/images/output-{hd_name_or_path}/{hd_name_or_path}"
)
return path.absolute()
def dynamic_img_path(self, format: str) -> str:
# TODO
return ""
...@@ -20,17 +20,22 @@ ...@@ -20,17 +20,22 @@
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import typing as tp import abc
import simbricks.orchestration.experiments as exp import simbricks.orchestration.experiments as exp
from simbricks.orchestration.experiment.experiment_environment_new import ExpEnv from simbricks.orchestration.system import base as sys_base
from simbricks.orchestration.simulation import channel as sim_chan
from simbricks.orchestration.experiment import experiment_environment_new as exp_env
from simbricks.orchestration.instantiation import base as inst_base
class Simulator(object):
class Simulator(abc.ABC):
"""Base class for all simulators.""" """Base class for all simulators."""
def __init__(self, e: exp.Experiment) -> None: def __init__(self, e: exp.Experiment) -> None:
self.extra_deps: tp.List[Simulator] = [] self.extra_deps: list[Simulator] = []
self.name = '' self.name = ""
self.experiment = e self.experiment = e
self._components: set[sys_base.Component] = []
def resreq_cores(self) -> int: def resreq_cores(self) -> int:
""" """
...@@ -50,30 +55,51 @@ class Simulator(object): ...@@ -50,30 +55,51 @@ class Simulator(object):
def full_name(self) -> str: def full_name(self) -> str:
"""Full name of the simulator.""" """Full name of the simulator."""
return '' return ""
# pylint: disable=unused-argument # pylint: disable=unused-argument
def prep_cmds(self, env: ExpEnv) -> tp.List[str]: def prep_cmds(self, env: exp_env.ExpEnv) -> list[str]:
"""Commands to prepare execution of this simulator.""" """Commands to prepare execution of this simulator."""
return [] return []
# TODO: call this in subclasses
def _add_component(self, comp: sys_base.Channel) -> None:
self._components.add(comp)
def _chan_needs_instance(self, chan: sys_base.Channel) -> bool:
if (
chan.a.component in self._components
and chan.b.component in self._components
):
return False
return True
def _get_sock_path(
self, inst: inst_base.Instantiation, chan: sys_base.Channel
) -> tuple[sim_chan.Channel, inst_base.Socket] | tuple[None, None]:
if not self._chan_needs_instance(chan):
return None, None
channel = self.experiment.retrieve_or_create_channel(chan)
return channel, inst.get_socket_path(channel)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def run_cmd(self, env: ExpEnv) -> tp.Optional[str]: @abc.abstractmethod
def run_cmd(self, env: exp_env.ExpEnv) -> str:
"""Command to execute this simulator.""" """Command to execute this simulator."""
return None return ""
def dependencies(self): def dependencies(self) -> list[Simulator]:
"""Other simulators to execute before this one.""" """Other simulators to execute before this one."""
return [] return []
# Sockets to be cleaned up # Sockets to be cleaned up
# pylint: disable=unused-argument # pylint: disable=unused-argument
def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]: def sockets_cleanup(self, env: exp_env.ExpEnv) -> list[str]:
return [] return []
# sockets to wait for indicating the simulator is ready # sockets to wait for indicating the simulator is ready
# pylint: disable=unused-argument # pylint: disable=unused-argument
def sockets_wait(self, env: ExpEnv) -> tp.List[str]: def sockets_wait(self, env: exp_env.ExpEnv) -> list[str]:
return [] return []
def start_delay(self) -> int: def start_delay(self) -> int:
......
...@@ -27,19 +27,19 @@ import simbricks.orchestration.system.base as system_base ...@@ -27,19 +27,19 @@ import simbricks.orchestration.system.base as system_base
from simbricks.orchestration.experiment.experiment_environment_new import ExpEnv from simbricks.orchestration.experiment.experiment_environment_new import ExpEnv
class Channel(sim_base.Simulator): class Channel(sim_base.Simulator):
def __init__(self, e: exp.Experiment): def __init__(self, e: exp.Experiment, chan: system_base.Channel):
super().__init__(e) super().__init__(e)
self.sync_period = 500 # nano second self._synchronized: bool = True
self.sys_channel: system_base.Channel = None self.sync_period: int = 500 # nano second
self.sys_channel: system_base.Channel = chan
def full_name(self) -> str: def full_name(self) -> str:
return 'channel.' + self.name return "channel." + self.name
def add(self, ch: system_base.Channel):
self.sys_channel = ch
self.name = f'{ch.id}'
self.experiment.sys_sim_map[ch] = self
# def add(self, ch: system_base.Channel):
# self.sys_channel = ch
# self.name = f"{ch.id}"
# self.experiment.sys_sim_map[ch] = self
# Copyright 2024 Max Planck Institute for Software Systems, and
# National University of Singapore
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
...@@ -20,83 +20,118 @@ ...@@ -20,83 +20,118 @@
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import simbricks.orchestration.simulation.base as base import abc
import simbricks.orchestration.system as system import sys
import typing as tp from
import simbricks.orchestration.experiments as exp from simbricks.orchestration import experiments
from simbricks.orchestration.experiment.experiment_environment_new import ExpEnv from simbricks.orchestration.simulation import base
from simbricks.orchestration.system import eth
from simbricks.orchestration.instantiation import base as inst_base
class NetSim(Simulator):
class NetSim(base.Simulator):
"""Base class for network simulators.""" """Base class for network simulators."""
def __init__(self, e: exp.Experiment) -> None: def __init__(self, e: exp.Experiment) -> None:
super().__init__(e) super().__init__(e)
self.opt = '' # TODO: do we want them here?
self.switches: tp.List[spec.Switch] = [] self._switch_specs = []
self.nicSim: tp.List[I40eNicSim] = [] self._host_specs = []
self.wait = False
def full_name(self) -> str: def full_name(self) -> str:
return 'net.' + self.name return "net." + self.name
def add(self, switch: spec.Switch):
self.switches.append(switch)
# switch.sim = self
self.experiment.add_network(self)
self.name = f'{switch.id}'
for ndev in switch.netdevs:
self.nicSim.append(n.net[0].sim)
def connect_sockets(self, env: ExpEnv) -> tp.List[tp.Tuple[Simulator, str]]:
sockets = []
for n in self.nicSim:
sockets.append((n, env.nic_eth_path(n)))
return sockets
def dependencies(self) -> tp.List[Simulator]: def dependencies(self) -> list[base.Simulator]:
# TODO
deps = [] deps = []
for s in self.switches: for s in self.switches:
for n in s.netdevs: for n in s.netdevs:
deps.append(n.net[0].sim) deps.append(n.net[0].sim)
return deps return deps
def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]: # TODO
def sockets_cleanup(self, env: exp_env.ExpEnv) -> list[str]:
pass pass
def sockets_wait(self, env: ExpEnv) -> tp.List[str]: # TODO
def sockets_wait(self, env: exp_env.ExpEnv) -> list[str]:
pass pass
def wait_terminate(self) -> bool: def wait_terminate(self) -> bool:
# TODO
return self.wait return self.wait
def init_network(self) -> None: def init_network(self) -> None:
pass pass
def sockets_cleanup(self, env: ExpEnv) -> tp.List[str]: def sockets_cleanup(self, env: exp_env.ExpEnv) -> list[str]:
cleanup = [] # TODO
return cleanup return []
class SwitchBMSim(NetSim): class SwitchNet(NetSim):
def __init__(self, e: exp.Experiment): def __init__(self, e: exp.Experiment) -> None:
super().__init__(e) super().__init__(e)
# TODO: probably we want to store these in a common base class...
self._switch_spec: eth.EthSwitch | None = None
def add_switch(self, switch_spec: eth.EthSwitch):
assert self._switch_spec is None
super()._add_component(switch_spec)
self._switch_spec = switch_spec
self.experimente.add_spec_sim_map(self._switch_spec, self)
def run_cmd(self, env: ExpEnv) -> str: def run_cmd(self, inst: inst_base.Instantiation) -> str:
cmd = env.repodir + '/sims/net/switch/net_switch' assert self._switch_spec is not None
cmd += f' -S {self.switches[0].sync_period} -E {self.switches[0].eth_latency}'
if not self.switches[0].sync: eth_latency = self._switch_spec.channels()[0].latency
cmd += ' -u' if any(lat != eth_latency for chan in self._switch_spec.channels()):
raise Exception("SwitchNet currently only supports single eth latency")
sync_period = None
run_sync = False
sockets: list[inst_base.Socket] = []
for chan in self._switch_spec.channels():
channel, socket = self._get_sock_path(inst=inst, chan=chan)
if channel is None or socket is None:
continue
sync_period = min(sync_period, channel.sync_period)
run_sync = run_sync or channel._synchronized
sock_paths.append(socket)
assert sync_period is not None
assert eth_latency is not None
cmd = env.repodir + "/sims/net/switch/net_switch"
cmd += f" -S {sync_period} -E {eth_latency}"
if not run_sync:
cmd += " -u"
if len(env.pcap_file) > 0: if len(env.pcap_file) > 0:
cmd += ' -p ' + env.pcap_file cmd += " -p " + env.pcap_file
for (_, n) in self.connect_sockets(env):
cmd += ' -s ' + n connect = ''
# for (_, n) in self.listen_sockets(env): listen = ''
# cmd += ' -h ' + n for sock in sockets:
if sock._type == inst_base.SockType.LISTEN:
listen += " -h " + sock._path
else:
connect += " -s " + sock._path
cmd += connect
cmd += listen
return cmd return cmd
# TODO
def sockets_cleanup(self, env: exp_env.ExpEnv) -> list[str]:
# cleanup here will just have listening eth sockets, switch also creates
# shm regions for each with a "-shm" suffix
cleanup = []
for s in super().sockets_cleanup(env):
cleanup.append(s)
cleanup.append(s + "-shm")
return cleanup
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment