Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default to add '-wdir' arguments #351

Merged
merged 4 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ MANIFEST
/docs/_build
.coverage
.ipynb_checkpoints
.vscode
/test/tmp/
16 changes: 5 additions & 11 deletions payu/envmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,15 @@ def module(command, *args):
exec(envs)


def lib_update(bin_path, lib_name):
def lib_update(required_libs, lib_name):
# Local import to avoid reversion interference
# TODO: Bad design, fixme!
# NOTE: We may be able to move this now that reversion is going away
from payu import fsops

# TODO: Use objdump instead of ldd
cmd = 'ldd {0}'.format(bin_path)
ldd_output = subprocess.check_output(shlex.split(cmd)).decode('ascii')
slibs = ldd_output.split('\n')

for lib_entry in slibs:
if lib_name in lib_entry:
lib_path = lib_entry.split()[2]

for lib_filename, lib_path in required_libs.items():
if lib_filename.startswith(lib_name) and lib_path.startswith('/apps/'):
jo-basevi marked this conversation as resolved.
Show resolved Hide resolved
# Load nci's /apps/ version of module if required
# pylint: disable=unbalanced-tuple-unpacking
mod_name, mod_version = fsops.splitpath(lib_path)[2:4]

Expand All @@ -115,4 +109,4 @@ def lib_update(bin_path, lib_name):
return '{0}/{1}'.format(mod_name, mod_version)

# If there are no libraries, return an empty string
return ''
aidanheerdegen marked this conversation as resolved.
Show resolved Hide resolved
return ''
32 changes: 12 additions & 20 deletions payu/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

# Local
from payu import envmod
from payu.fsops import mkdir_p, make_symlink, read_config, movetree
from payu.fsops import mkdir_p, make_symlink, read_config, movetree, required_libs
from payu.schedulers.pbs import get_job_info, pbs_env_init, get_job_id
from payu.models import index as model_index
import payu.profilers
Expand Down Expand Up @@ -138,6 +138,10 @@ def init_models(self):

submodels = self.config.get('submodels', [])

# Inject information about required dynamically loaded libraries into submodel configuration
for sm in submodels:
jo-basevi marked this conversation as resolved.
Show resolved Hide resolved
sm['required_libs'] = required_libs(sm['exe'])

solo_model = self.config.get('model')
if not solo_model:
sys.exit('payu: error: Unknown model configuration.')
Expand Down Expand Up @@ -514,19 +518,18 @@ def run(self, *user_flags):
# Update MPI library module (if not explicitly set)
# TODO: Check for MPI library mismatch across multiple binaries
if mpi_module is None:
mpi_module = envmod.lib_update(
model.exec_path_local,
envmod.lib_update(
model.config.get('required_libs'),
'libmpi.so'
)

model_prog = []

if mpi_module.startswith('openmpi'):
# Our MPICH wrapper does not support a working directory flag
model_prog.append('-wdir {0}'.format(model.work_path))
elif self.config.get('scheduler') == 'slurm':
# Slurm's launcher controls the working directory
model_prog.append('--chdir {0}'.format(model.work_path))
wdir_arg = '-wdir'
if self.config.get('scheduler') == 'slurm':
# Option to set the working directory differs in slurm
wdir_arg = '--chdir'
model_prog.append(f'{wdir_arg} {model.work_path}')

# Append any model-specific MPI flags
model_flags = model.config.get('mpiflags', [])
Expand Down Expand Up @@ -590,13 +593,6 @@ def run(self, *user_flags):
if self.config.get('coredump', False):
enable_core_dump()

# Our MVAPICH wrapper does not support working directories
if mpi_module.startswith('mvapich'):
curdir = os.getcwd()
os.chdir(self.work_path)
else:
curdir = None

Comment on lines -593 to -599
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to leave a comment to say deleting this relates directly to #350.

This just can't work with submodels and there is no attempt to test for that, nor do we want to support something that is so limited, so it is being removed.

# Dump out environment
with open(self.env_fname, 'w') as file:
file.write(yaml.dump(dict(os.environ), default_flow_style=False))
Expand All @@ -617,10 +613,6 @@ def run(self, *user_flags):
else:
rc = sp.call(shlex.split(cmd), stdout=f_out, stderr=f_err)

# Return to control directory
if curdir:
os.chdir(curdir)

f_out.close()
f_err.close()

Expand Down
27 changes: 27 additions & 0 deletions payu/fsops.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import os
import shutil
import sys
import shlex
import subprocess

# Extensions
import yaml
Expand Down Expand Up @@ -171,3 +173,28 @@ def is_conda():
"""Return True if python interpreter is in a conda environment"""

return os.path.exists(os.path.join(sys.prefix, 'conda-meta'))


def parse_ldd_output(ldd_output):
"""Parses the string output from ldd and returns a dictionary of lib filename and fullpath pairs"""
needed_libs = {}
for line in ldd_output.split("\n"):
word_list = line.split()
if len(word_list) >= 3 and word_list[1] == '=>':
needed_libs[word_list[0]] = word_list[2]
return needed_libs


def required_libs(bin_path):
"""
Runs ldd command and parses the output.
This function should only be called once per binary
i.e. Use a singleton pattern in the caller object.
PARAMETERS:
string bin_path: full path to the binary
RETURN:
dict: {filename-of-lib: fullpath-of-file}
"""
cmd = 'ldd {0}'.format(bin_path)
ldd_out = subprocess.check_output(shlex.split(cmd)).decode('ascii')
return parse_ldd_output(ldd_out)
3 changes: 2 additions & 1 deletion payu/models/fms.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from payu.models.model import Model
from payu import envmod
from payu.fsops import required_libs

# There is a limit on the number of command line arguments in a forked
# MPI process. This applies only to mppnccombine-fast. The limit is higher
Expand Down Expand Up @@ -109,7 +110,7 @@ def fms_collate(model):
# and mppnccombine-fast uses an explicit -o flag to specify
# the output
collate_flags = " ".join([collate_flags, '-o'])
envmod.lib_update(mppnc_path, 'libmpi.so')
envmod.lib_update(required_libs(mppnc_path), 'libmpi.so')

# Import list of collated files to ignore
collate_ignore = collate_config.get('ignore')
Expand Down
5 changes: 5 additions & 0 deletions test/resources/sample_ldd_output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
linux-vdso.so.1 (0x00007ffd60799000)
libmpi_usempif08_Intel.so.40 => /apps/openmpi/4.0.2/lib/libmpi_usempif08_Intel.so.40 (0x00007fa492a7c000)
libmpi_usempi_ignore_tkr_Intel.so.40 => /apps/openmpi/4.0.2/lib/libmpi_usempi_ignore_tkr_Intel.so.40 (0x00007fa492863000)
libmpi.so.40 => /apps/openmpi/4.0.2/lib/libmpi.so.40 (0x00007fa493665000)
libmpi_mpifh_Intel.so.40 => /apps/openmpi/4.0.2/lib/libmpi_mpifh_Intel.so.40 (0x00007fa4925cb000)
28 changes: 28 additions & 0 deletions test/test_payu.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import payu
import payu.fsops
import payu.laboratory
import payu.envmod

from .common import testdir, tmpdir, ctrldir, labdir, workdir
from .common import make_exe, make_inputs, make_restarts, make_all_files
Expand Down Expand Up @@ -214,3 +215,30 @@ def test_lab_new():
sys.stdout = StringIO()
lab = payu.laboratory.Laboratory('model')
sys.stdout = sys.__stdout__


def test_parse_ldd_output():
ldd_output_path = os.path.join('test', 'resources', 'sample_ldd_output.txt')
with open(ldd_output_path, 'r') as f:
ldd_output = f.read()
required_libs = payu.fsops.parse_ldd_output(ldd_output)
assert(len(required_libs), 4)
assert(required_libs['libmpi.so.40'], '/apps/openmpi/4.0.2/lib/libmpi.so.40')


def test_lib_update_lib_if_required():
required_libs_dict = {
'libmpi.so.40': '/apps/openmpi/4.0.2/lib/libmpi.so.40',
'libmpi_usempif08_Intel.so.40': '/apps/openmpi/4.0.2/lib/libmpi_usempif08_Intel.so.40'
}
result = payu.envmod.lib_update(required_libs_dict, 'libmpi.so')
assert(result == 'openmpi/4.0.2')


def test_lib_update_if_nci_module_not_required():
required_libs_dict = {
'libmpi.so.40': '/$HOME/spack-microarchitectures.git/opt/spack/linux-rocky8-cascadelake/intel-2019.5.281/openmpi-4.1.5-ooyg5wc7sa3tvmcpazqqb44pzip3wbyo/lib/libmpi.so.40',
'libmpi_usempif08.so.40': '/$HOME/exe/spack-microarchitectures.git/opt/spack/linux-rocky8-cascadelake/intel-2019.5.281/openmpi-4.1.5-ooyg5wc7sa3tvmcpazqqb44pzip3wbyo/lib/libmpi_usempif08.so.40',
}
result = payu.envmod.lib_update(required_libs_dict, 'libmpi.so')
assert(result == '')
Loading