diff --git a/.gitignore b/.gitignore index 0fe38b9..a22541b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,167 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class -.vscode -build -# Prerequisites -*.d +# C extensions +*.so -# Compiled Object files -*.slo -*.lo -*.o -*.obj +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST -# Precompiled Headers -*.gch -*.pch +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec -# Compiled Dynamic libraries -*.so -*.dylib -*.dll +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ -# Fortran module files -*.mod -*.smod +# pytype static type analyzer +.pytype/ -# Compiled Static libraries -*.lai -*.la -*.a -*.lib +# Cython debug symbols +cython_debug/ -# Executables -*.exe -*.out -*.app +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ # Custom py_interface/build/ py_interface/dist/ py_interface/ns3_ai.egg-info/ +cmake-build-debug/ +examples/elegante/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..24f4727 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,24 @@ +set(name ns3-ai) + +set(source_files + model/memory-pool.cc + ) + +set(header_files + model/memory-pool.h + model/train-var.h + model/ns3-ai-rl.h + model/ns3-ai-dl.h + ) + +set(libraries_to_link + libcore + ) + +build_lib( + LIBNAME ${name} + SOURCE_FILES ${source_files} + HEADER_FILES ${header_files} + LIBRARIES_TO_LINK ${libraries_to_link} +) + diff --git a/examples/rl-tcp/run_rl_tcp.py b/examples/rl-tcp/run_rl_tcp.py index a55205a..601c886 100644 --- a/examples/rl-tcp/run_rl_tcp.py +++ b/examples/rl-tcp/run_rl_tcp.py @@ -141,7 +141,7 @@ def learn(self, ): if args.use_rl: dqn = DQN() -exp = Experiment(1234, 4096, 'rl-tcp', '../../') +exp = Experiment(1234, 4096, 'rl-tcp/', '../../') exp.run(show_output=1) try: while not var.isFinish(): diff --git a/examples/run_example.sh b/examples/run_example.sh new file mode 100755 index 0000000..6935562 --- /dev/null +++ b/examples/run_example.sh @@ -0,0 +1,36 @@ +# validity checks +if [[ "$(dirname "$0")" != "." ]]; then + echo '[bash ] please change to the folder containing this script before execution. Exiting...' + exit 1 +fi + +TARGET=$1 +if [ ! -f $TARGET ]; then + echo "[bash ] invalid example script file name. Exiting..." + exit 1 +fi + +# parsing input script path into example folder to be copied and the script file name. +IFS='/'; arrIN=($TARGET); unset IFS; +EXAMPLE_DIR=${arrIN[0]} +RUN_FILE=${arrIN[1]} + +# copy latest version of the example to the scratch folder, assuming it lies 3 levels above the examples folder. +SCRATCH_DIR=../../../scratch +TARGET_DIR=$SCRATCH_DIR/$EXAMPLE_DIR +if [[ -d $TARGET_DIR ]] +then + echo "[bash ] deleting existing example in scratch folder" + rm -rf $TARGET_DIR +fi +echo "[bash ] copying example to scratch folder" +cp -r $EXAMPLE_DIR $SCRATCH_DIR +echo "[bash ] copied example to scratch folder" + +# cd there and run the script with the python script arguments +pushd $TARGET_DIR > /dev/null || exit +shift # removes the first argument from "$@" (which is the python file accessible via $TARGET) +echo "[bash ] running python entry script" +python $RUN_FILE "$@" +popd > /dev/null || exit +bash ../freeshm.sh \ No newline at end of file diff --git a/freeshm.sh b/freeshm.sh old mode 100644 new mode 100755 diff --git a/model/memory-pool.cc b/model/memory-pool.cc index b2456ab..9e0278a 100644 --- a/model/memory-pool.cc +++ b/model/memory-pool.cc @@ -235,8 +235,10 @@ void SharedMemoryPool::ReleaseMemory (uint16_t id) //Should register first { NS_LOG_FUNCTION (this << "ID: " << id); SharedMemoryLockable *info = m_memoryLocker[id]; + NS_LOG_INFO ("memory version before: " << +info->version << " , nextVersion: " << +info->nextVersion); while (!__sync_bool_compare_and_swap(&info->version, info->nextVersion - (uint8_t)1, info->nextVersion)) ShmYield(); + NS_LOG_INFO ("memory version after: " << +info->version << " , nextVersion: " << +info->nextVersion); } void @@ -244,8 +246,10 @@ SharedMemoryPool::ReleaseMemoryAndRollback (uint16_t id) { NS_LOG_FUNCTION (this << "ID: " << id); SharedMemoryLockable *info = m_memoryLocker[id]; + NS_LOG_INFO ("memory version before: " << +info->version << " , nextVersion: " << +info->nextVersion); while (!__sync_bool_compare_and_swap(&info->nextVersion, info->version + (uint8_t)1, info->version)) ShmYield(); + NS_LOG_INFO ("memory version after: " << +info->version << " , nextVersion: " << +info->nextVersion); } uint8_t diff --git a/model/ns3-ai-rl.h b/model/ns3-ai-rl.h index cde192a..645806f 100644 --- a/model/ns3-ai-rl.h +++ b/model/ns3-ai-rl.h @@ -179,6 +179,12 @@ class Ns3AIRL : public SimpleRefCount> * \return finish flag. */ bool GetIsFinish(void); + + /** + * TODO comment + * @return + */ + uint8_t GetMemVersion(void); }; template @@ -546,4 +552,10 @@ bool Ns3AIRL::GetIsFinish(void) return *m_isFinish; } +template +uint8_t Ns3AIRL::GetMemVersion(void) +{ + return SharedMemoryPool::Get()->GetMemoryVersion(m_id); +} + } // namespace ns3 \ No newline at end of file diff --git a/py_interface/memory-pool.c b/py_interface/memory-pool.c index 4eb9301..8f9ad7b 100644 --- a/py_interface/memory-pool.c +++ b/py_interface/memory-pool.c @@ -68,7 +68,7 @@ static void *GetMemory(uint16_t id, uint32_t size) { if (id >= MAX_ID) { - PyErr_SetString(PyExc_RuntimeError, "Id out of range"); + PyErr_SetString(PyExc_RuntimeError, "Id out of range (upper exclusive limit is %u)"); return NULL; } CtrlInfoLock(); @@ -80,7 +80,7 @@ static void *GetMemory(uint16_t id, uint32_t size) if (gMemoryCtrlInfo[gCurCtrlInfo->id] != 0) { CtrlInfoUnlock(); - PyErr_Format(PyExc_RuntimeError, "Id %u has been used", gCurCtrlInfo->id); + PyErr_Format(PyExc_RuntimeError, "Id %u has been used already", gCurCtrlInfo->id); return NULL; } gMemoryCtrlInfo[gCurCtrlInfo->id] = gCurCtrlInfo; @@ -92,7 +92,8 @@ static void *GetMemory(uint16_t id, uint32_t size) if (size != gMemoryCtrlInfo[id]->size) { CtrlInfoUnlock(); - PyErr_Format(PyExc_RuntimeError, "Size of memory error(%u %u)", size, gMemoryCtrlInfo[id]->size); + PyErr_Format(PyExc_RuntimeError, "Size mismatch in (requested, allocated) memory: (%u %u)", + size, gMemoryCtrlInfo[id]->size); return NULL; } CtrlInfoUnlock(); diff --git a/py_interface/ns3_util.py b/py_interface/ns3_util.py index 22ac51b..ed14a15 100644 --- a/py_interface/ns3_util.py +++ b/py_interface/ns3_util.py @@ -20,10 +20,11 @@ import os import subprocess import time -from collections import OrderedDict from copy import copy +from pathlib import Path import psutil +import shutil try: from collections.abc import Iterable @@ -99,31 +100,41 @@ def get_setting(setting_map): def build_ns3(path): - print('build') - proc = subprocess.Popen('./waf build', shell=True, stdout=subprocess.PIPE, + print('=== NS3AI: BUILDING NS3 ===') + proc = subprocess.Popen('./ns3 build', shell=True, stdout=subprocess.PIPE, stderr=devnull, universal_newlines=True, cwd=path) proc.wait() - ok = False - for line in proc.stdout: - if "'build' finished successfully" in line: - ok = True - break + + ok = proc.returncode == 0 return ok -def run_single_ns3(path, pname, setting=None, env=None, show_output=False, build=True): +def check_program_installed(program_name: str) -> str: + program_path = shutil.which(program_name) + if program_path is None: + print("Executable '{program}' was not found".format(program=program_name.capitalize())) + exit(-1) + return program_path + + +def run_single_ns3(path, pname, setting=None, env=None, show_output=False, profile_ns3=False, build=True): if build and not build_ns3(path): - return None + raise RuntimeError("run_single_ns3(): requested to build ns3, but build failed!") if env: env.update(os.environ) env['LD_LIBRARY_PATH'] = os.path.abspath(os.path.join(path, 'build', 'lib')) - if not setting: - cmd = './{}'.format(pname) - else: - cmd = './{}{}'.format(pname, get_setting(setting)) exec_path = os.path.join(path, 'build', 'scratch') - if os.path.isdir(os.path.join(exec_path, pname)): - exec_path = os.path.join(exec_path, pname) + # TODO hotfix for cmake-built ns3: executable seems to be placed differently, so take 1st file from exec_path + exec_name = [f for f in os.listdir(os.path.join(exec_path, pname)) if f.startswith("ns")][0] + cmd: str = f'./{pname}/{exec_name}' + if setting: + cmd += get_setting(setting) + if profile_ns3: + perf_cmd = check_program_installed("perf") + base_out_dir = str(setting["outDir"]) if (setting is not None and "outDir" in setting) else "." + perf_out_fp = Path(base_out_dir) / "perf.data" + perf_options = f"record -o {str(perf_out_fp.resolve())} -g -e cpu-cycles,context-switches" + cmd = f"{perf_cmd} {perf_options} bash -c \'{cmd}\'k" # TODO (later): add option -a for run_bulk_ns3() if show_output: proc = subprocess.Popen( cmd, shell=True, universal_newlines=True, cwd=exec_path, env=env) diff --git a/py_interface/py_interface.py b/py_interface/py_interface.py index 3dcccbc..3ec4e3b 100644 --- a/py_interface/py_interface.py +++ b/py_interface/py_interface.py @@ -26,6 +26,7 @@ AcquireMemoryTarget, FreeMemory, GetMemory, GetMemoryVersion, IncMemoryVersion, Init, RegisterMemory, ReleaseMemory, ReleaseMemoryRB, Reset, ResetAll) +import os READABLE = 0xff SETABLE = 0 @@ -116,6 +117,7 @@ def __init__(self, uid, EnvType, ActType, ExtInfo=EmptyInfo): self.actType = ActType self.extInfo = ExtInfo self.finished = False + self.rollback_on_release = False # the main field for RL class StorageType(Structure): @@ -144,6 +146,9 @@ def SetCond(self, mod, res): self.mod = mod self.res = res + def SetRollbackOnRelease(self, value: bool): + self.rollback_on_release = value + # acquire ns-3's data in the memory def Acquire(self): while not self.isFinish() and self.GetVersion() % self.mod != self.res: @@ -166,7 +171,10 @@ def __enter__(self): # ensure Ctrl+C can interrupt the function def __exit__(self, Type, value, traceback): if self.finished: return - self.Release() + if self.rollback_on_release: + self.ReleaseAndRollback() + else: + self.Release() # This class established an environment for ns3 and python # to exchange data with the share memory @@ -245,7 +253,8 @@ class Experiment: # \param[in] memSize : share memory size # \param[in] programName : program name of ns3 # \param[in] path : current working directory - def __init__(self, shmKey, memSize, programName, path): + # \param[in] no_build : if specified, start out with self.dirty = False (i.e. don't build at startup) + def __init__(self, shmKey, memSize, programName, path, build_ns3=True): if self._created: raise Exception('Experiment is singleton') self._created = True @@ -254,7 +263,7 @@ def __init__(self, shmKey, memSize, programName, path): self.programName = programName self.path = path self.proc = None - self.dirty = True + self.dirty = build_ns3 Init(shmKey, memSize) def __del__(self): @@ -265,12 +274,15 @@ def __del__(self): # run ns3 script in cmd with the setting being input # \param[in] setting : ns3 script input parameters(default : None) # \param[in] show_output : whether to show output or not(default : False) - def run(self, setting=None, show_output=False): + def run(self, setting=None, show_output=False, log_modules=[], log_level="none", profile_ns3=False): self.kill() env = {'NS_GLOBAL_VALUE': 'SharedMemoryKey={};SharedMemoryPoolSize={};'.format( self.shmKey, self.memSize)} + if log_level != "none" and len(log_modules) > 0: + env["NS_LOG"] = ":".join([f"{module}=level_{log_level}|prefix_level" for module in log_modules]) self.proc = run_single_ns3( - self.path, self.programName, setting, env=env, show_output=show_output, build=self.dirty) + self.path, self.programName, setting, env=env, show_output=show_output, + profile_ns3=profile_ns3, build=self.dirty) self.dirty = False return self.proc diff --git a/py_interface/setup.py b/py_interface/setup.py index 528ac66..000509d 100644 --- a/py_interface/setup.py +++ b/py_interface/setup.py @@ -42,7 +42,7 @@ author_email="eic_lpy@hust.edu.cn", url="placeholder", packages=setuptools.find_packages(), - install_requires=["psutil==5.7.2"], + install_requires=["psutil>=5.7.2"], classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",