251 lines
8.5 KiB
Python
251 lines
8.5 KiB
Python
""" This file contains components that tie closely with the FireSim switch
|
|
models that live in target-design/switch/ """
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import random
|
|
import string
|
|
import logging
|
|
from fabric.api import local # type: ignore
|
|
|
|
from runtools.utils import is_on_aws
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from runtools.firesim_topology_elements import FireSimSwitchNode
|
|
|
|
rootLogger = logging.getLogger()
|
|
|
|
|
|
class AbstractSwitchToSwitchConfig:
|
|
"""This class is responsible for providing functions that take a FireSimSwitchNode
|
|
and emit the correct config header to produce an actual switch simulator binary
|
|
that behaves as defined in the FireSimSwitchNode.
|
|
|
|
This assumes that the switch has already been assigned to a host."""
|
|
|
|
fsimswitchnode: FireSimSwitchNode
|
|
build_disambiguate: str
|
|
|
|
def __init__(self, fsimswitchnode: FireSimSwitchNode) -> None:
|
|
"""Construct the switch's config file"""
|
|
self.fsimswitchnode = fsimswitchnode
|
|
# this lets us run many builds in parallel without conflict across
|
|
# parallel experiments which may have overlapping switch ids
|
|
self.build_disambiguate = "".join(
|
|
random.choice(string.ascii_uppercase + string.digits) for _ in range(64)
|
|
)
|
|
|
|
def emit_init_for_uplink(self, uplinkno: int) -> str:
|
|
"""Emit an init for a switch to talk to it's uplink."""
|
|
|
|
linkobj = self.fsimswitchnode.uplinks[uplinkno]
|
|
upperswitch = linkobj.get_uplink_side()
|
|
|
|
target_local_portno = len(self.fsimswitchnode.downlinks) + uplinkno
|
|
if linkobj.link_crosses_hosts():
|
|
uplinkhostip = (
|
|
linkobj.link_hostserver_host()
|
|
) # upperswitch.host_instance.get_private_ip()
|
|
uplinkhostport = linkobj.link_hostserver_port()
|
|
|
|
return (
|
|
"new SocketClientPort("
|
|
+ str(target_local_portno)
|
|
+ ', "'
|
|
+ uplinkhostip
|
|
+ '", '
|
|
+ str(uplinkhostport)
|
|
+ ");\n"
|
|
)
|
|
|
|
else:
|
|
linkbasename = linkobj.get_global_link_id()
|
|
return (
|
|
"new ShmemPort("
|
|
+ str(target_local_portno)
|
|
+ ', "'
|
|
+ linkbasename
|
|
+ '", true);\n'
|
|
)
|
|
|
|
def emit_init_for_downlink(self, downlinkno: int) -> str:
|
|
"""emit an init for the specified downlink."""
|
|
downlinkobj = self.fsimswitchnode.downlinks[downlinkno]
|
|
downlink = downlinkobj.get_downlink_side()
|
|
if downlinkobj.link_crosses_hosts():
|
|
hostport = downlinkobj.link_hostserver_port()
|
|
# create a SocketServerPort
|
|
return (
|
|
"new SocketServerPort("
|
|
+ str(downlinkno)
|
|
+ ", "
|
|
+ str(hostport)
|
|
+ ");\n"
|
|
)
|
|
else:
|
|
linkbasename = downlinkobj.get_global_link_id()
|
|
return (
|
|
"new ShmemPort("
|
|
+ str(downlinkno)
|
|
+ ', "'
|
|
+ linkbasename
|
|
+ '", false);\n'
|
|
)
|
|
|
|
def emit_switch_configfile(self) -> str:
|
|
"""Produce a config file for the switch generator for this switch"""
|
|
constructedstring = ""
|
|
constructedstring += self.get_header()
|
|
constructedstring += self.get_numclientsconfig()
|
|
constructedstring += self.get_portsetup()
|
|
constructedstring += self.get_mac2port()
|
|
return constructedstring
|
|
|
|
# produce mac2port array portion of config
|
|
def get_mac2port(self) -> str:
|
|
"""This takes a python array that represents the mac to port mapping,
|
|
and converts it to a C++ array"""
|
|
|
|
mac2port_pythonarray = self.fsimswitchnode.switch_table
|
|
assert mac2port_pythonarray is not None
|
|
|
|
commaseparated = ""
|
|
for elem in mac2port_pythonarray:
|
|
commaseparated += str(elem) + ", "
|
|
|
|
# remove extraneous ", "
|
|
commaseparated = commaseparated[:-2]
|
|
commaseparated = "{" + commaseparated + "};"
|
|
|
|
retstr = """
|
|
#ifdef MACPORTSCONFIG
|
|
uint16_t mac2port[{}] {}
|
|
#endif
|
|
""".format(
|
|
len(mac2port_pythonarray), commaseparated
|
|
)
|
|
return retstr
|
|
|
|
def get_header(self) -> str:
|
|
"""Produce file header."""
|
|
retstr = """// THIS FILE IS MACHINE GENERATED. SEE deploy/buildtools/switchmodelconfig.py
|
|
"""
|
|
return retstr
|
|
|
|
def get_numclientsconfig(self) -> str:
|
|
"""Emit constants for num ports."""
|
|
numdownlinks = len(self.fsimswitchnode.downlinks)
|
|
numuplinks = len(self.fsimswitchnode.uplinks)
|
|
totalports = numdownlinks + numuplinks
|
|
|
|
retstr = """
|
|
#ifdef NUMCLIENTSCONFIG
|
|
#define NUMPORTS {}
|
|
#define NUMDOWNLINKS {}
|
|
#define NUMUPLINKS {}
|
|
#endif""".format(
|
|
totalports, numdownlinks, numuplinks
|
|
)
|
|
return retstr
|
|
|
|
def get_portsetup(self) -> str:
|
|
"""emit port intialisations."""
|
|
initstring = ""
|
|
for downlinkno in range(len(self.fsimswitchnode.downlinks)):
|
|
initstring += (
|
|
"ports["
|
|
+ str(downlinkno)
|
|
+ "] = "
|
|
+ self.emit_init_for_downlink(downlinkno)
|
|
)
|
|
|
|
for uplinkno in range(len(self.fsimswitchnode.uplinks)):
|
|
initstring += (
|
|
"ports["
|
|
+ str(len(self.fsimswitchnode.downlinks) + uplinkno)
|
|
+ "] = "
|
|
+ self.emit_init_for_uplink(uplinkno)
|
|
)
|
|
|
|
retstr = """
|
|
#ifdef PORTSETUPCONFIG
|
|
{}
|
|
#endif
|
|
""".format(
|
|
initstring
|
|
)
|
|
return retstr
|
|
|
|
def switch_binary_name(self) -> str:
|
|
return "switch" + str(self.fsimswitchnode.switch_id_internal)
|
|
|
|
def buildswitch(self) -> None:
|
|
"""Generate the config file, build the switch."""
|
|
|
|
configfile = self.emit_switch_configfile()
|
|
binaryname = self.switch_binary_name()
|
|
|
|
switchorigdir = self.switch_build_local_dir()
|
|
switchbuilddir = (
|
|
switchorigdir + binaryname + "-" + self.build_disambiguate + "-build/"
|
|
)
|
|
|
|
rootLogger.info(
|
|
"Building switch model binary for switch " + str(self.switch_binary_name())
|
|
)
|
|
|
|
rootLogger.debug(str(configfile))
|
|
|
|
def local_logged(command: str) -> None:
|
|
"""Run local command with logging."""
|
|
localcap = local(command, capture=True)
|
|
rootLogger.debug(localcap)
|
|
rootLogger.debug(localcap.stderr)
|
|
|
|
# make a build dir for this switch
|
|
local_logged("mkdir -p " + switchbuilddir)
|
|
local_logged("cp " + switchorigdir + "*.h " + switchbuilddir)
|
|
local_logged("cp " + switchorigdir + "*.cc " + switchbuilddir)
|
|
local_logged("cp " + switchorigdir + "Makefile " + switchbuilddir)
|
|
|
|
text_file = open(switchbuilddir + "switchconfig.h", "w")
|
|
text_file.write(configfile)
|
|
text_file.close()
|
|
local_logged("cd " + switchbuilddir + " && make")
|
|
local_logged("mv " + switchbuilddir + "switch " + switchbuilddir + binaryname)
|
|
|
|
def get_switch_simulation_command(self) -> str:
|
|
"""Return the command to boot the switch."""
|
|
switchlatency = self.fsimswitchnode.switch_switching_latency
|
|
linklatency = self.fsimswitchnode.switch_link_latency
|
|
bandwidth = self.fsimswitchnode.switch_bandwidth
|
|
# insert gdb -ex run --args in front of ./ below to start switches in gdb
|
|
return """screen -S {} -d -m bash -c "script -f -c './{} {} {} {}' switchlog"; sleep 1""".format(
|
|
self.switch_binary_name(),
|
|
self.switch_binary_name(),
|
|
linklatency,
|
|
switchlatency,
|
|
bandwidth,
|
|
)
|
|
|
|
def kill_switch_simulation_command(self) -> str:
|
|
"""Return the command to kill the switch."""
|
|
need_sudo = "sudo" if is_on_aws() else ""
|
|
return f"""{need_sudo} pkill -f -SIGKILL {self.switch_binary_name()}"""
|
|
|
|
def switch_build_local_dir(self) -> str:
|
|
"""get local build dir of the switch."""
|
|
return "../target-design/switch/"
|
|
|
|
def switch_binary_local_path(self) -> str:
|
|
"""return the full local path where the switch binary lives."""
|
|
binaryname = self.switch_binary_name()
|
|
switchorigdir = self.switch_build_local_dir()
|
|
switchbuilddir = (
|
|
switchorigdir + binaryname + "-" + self.build_disambiguate + "-build/"
|
|
)
|
|
return switchbuilddir + self.switch_binary_name()
|