- made IprCluster target host work. This target host provides access to intel ifort and intel mkl packages, which are available as environment modules.
- added a unit test that validates the IprCluster (only works on ipr cluster node)
- also improved unit tests to use TMPDIR environment variable if present, in order to avoid permission issues in case the test is run by different users on the same system.
- made the benchmark display an error in case starbench fails

work related to [https://bugzilla.ipr.univ-rennes.fr/show_bug.cgi?id=3958]
This commit is contained in:
Guillaume Raffy 2024-11-26 13:33:12 +01:00
parent 2f2ebdcf35
commit 7d019f74dd
9 changed files with 176 additions and 17 deletions

View File

@ -22,7 +22,16 @@ pipeline {
sh """#!/bin/bash
set -o errexit
source ${IPRBENCH_VENV_PATH}/bin/activate &&
python -m unittest
python -m unittest ./test
"""
}
}
stage('testing (tests that only work on ipr cluster nodes)') {
steps {
sh """#!/bin/bash
set -o errexit
source ${IPRBENCH_VENV_PATH}/bin/activate &&
python -m unittest ./ci/test_iprcluster.py
"""
}
}

67
ci/test_iprcluster.py Normal file
View File

@ -0,0 +1,67 @@
"""tests specific for IPR cluster
"""
import unittest
import logging
import subprocess
import json
from pathlib import Path
from shutil import rmtree
from os import getenv
from iprbench.core import ResultsDbParams, HostTypeId
from cocluto.SimpaDbUtil import SshAccessedMysqlDb
def test_resultsdb(resultsdb_params: ResultsDbParams, results_root_path: Path):
results_db_type = resultsdb_params['type']
logging.info('testing resultsdb %s', results_db_type)
logging.info('resultsdb_params : %s', json.dumps(resultsdb_params))
results_dir = results_root_path / results_db_type
if results_dir.exists():
rmtree(results_dir)
results_dir.mkdir(parents=True)
benchmark_id = 'mamul1'
benchmark_config = {
'fortran_compiler': 'ifort:<default>',
'blas_library': 'intelmkl:<default>',
'matrix_size': 1024,
'num_loops': 10,
'num_cores': 2,
'launcher': 'iprbench.unittest.iprcluster',
}
target_system_type_id = HostTypeId('fr.univ-rennes.ipr.cluster-node')
command = f'iprbench-run --benchmark-id \'{benchmark_id}\' --config \'{json.dumps(benchmark_config)}\' --results-dir {results_dir} --resultsdb-params \'{json.dumps(resultsdb_params)}\' --target-system-type-id "{target_system_type_id}"'
subprocess.run(command, shell=True, check=True, executable='/bin/bash')
class IprClusterTestCase(unittest.TestCase):
results_root_dir: Path
def setUp(self) -> None: # pylint: disable=useless-parent-delegation
self.results_root_dir = Path(f'{getenv("TMPDIR", default="/tmp")}/iprbenchs/test_results/resultsdb')
if self.results_root_dir.exists():
rmtree(self.results_root_dir)
self.results_root_dir.mkdir(parents=True)
return super().setUp()
def test_sqlserver(self):
db_server_fqdn = 'iprbenchsdb.ipr.univ-rennes1.fr'
db_user = 'test_iprbenchw'
db_name = 'test_iprbenchs'
ssh_user = 'test_iprbenchw'
sql_backend = SshAccessedMysqlDb(db_server_fqdn, db_user, db_name, ssh_user)
if sql_backend.table_exists('mamul1'):
sql_backend.delete_table('mamul1')
resultsdb_params = {
'type': 'sqlserver-viassh-database',
'db_server_fqdn': db_server_fqdn,
'db_user': db_user,
'db_name': db_name,
'ssh_user': ssh_user
}
test_resultsdb(resultsdb_params, self.results_root_dir)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
unittest.main()

View File

@ -85,7 +85,9 @@ class HiBench(IBenchmark):
if len(env_vars_bash_commands) > 0:
shell_command += f'{env_vars_bash_commands} && '
shell_command += f'{get_proxy_env_vars()} starbench --source-tree-provider \'{source_tree_provider}\' --num-cores {num_cores} --output-dir={output_dir} --cmake-path={cmake_path} {" ".join([f"--cmake-option={option}" for option in cmake_options])} --benchmark-command=\'{benchmark_command}\' --output-measurements={output_measurements_file_path}'
subprocess.run(shell_command, shell=True, check=True, executable='/bin/bash')
completed_process = subprocess.run(shell_command, shell=True, check=False, executable='/bin/bash') # for some reason, module is only found when executable is /bin/bash
if completed_process.returncode != 0:
assert False, f'the shell command "{shell_command}" failed. See details in {output_dir}'
measurements: BenchmarkMeasurements = {}
starbench_results = StarbenchResults(output_measurements_file_path)

View File

@ -76,7 +76,9 @@ class MaMul1(IBenchmark):
shell_command += f'{env_vars_bash_commands} && '
shell_command += f'starbench --source-tree-provider \'{source_tree_provider}\' --num-cores {num_cores} --output-dir={output_dir} --cmake-path=/usr/bin/cmake {" ".join([f"--cmake-option={option}" for option in cmake_options])} --benchmark-command=\'{" ".join(benchmark_command)}\' --output-measurements={output_measurements_file_path}'
logging.debug('shell_command = "%s"', shell_command)
subprocess.run(shell_command, shell=True, check=True, encoding='/bin/bash')
completed_process = subprocess.run(shell_command, shell=True, check=False, executable='/bin/bash') # for some reason, module is only found when executable is /bin/bash
if completed_process.returncode != 0:
assert False, f'the shell command "{shell_command}" failed. See details in {output_dir}'
measurements: BenchmarkMeasurements = {}
starbench_results = StarbenchResults(output_measurements_file_path)
measurements['duration_avg'] = starbench_results.get_average_duration()

View File

@ -40,7 +40,7 @@ class HibenchResultsParser():
# module-whatis Sets the Environment to use the Intel Math Kernel Libraries version 2020.0.1
# setenv MKLROOT /opt/intel/compilers_and_libraries_2020.1.217/linux/mkl
lib_info['lib_name'] = 'mkl'
lib_info['lib_name'] = 'intelmkl'
lib_info['lib_version'] = '2020.0.1'
break
elif path.find('/usr/lib/libblas.so') != -1:

47
iprbench/envmodules.py Normal file
View File

@ -0,0 +1,47 @@
from typing import Set
from pathlib import Path
import subprocess
import re
EnvModule = str # eg compilers/ifort/latest
def get_available_modules() -> Set[EnvModule]:
available_modules = set()
completed_process = subprocess.run('module avail --terse', executable='/bin/bash', shell=True, capture_output=True, check=True) # for some reason, module is only found when executable is /bin/bash
# (iprbench.venv) graffy@alambix50:/opt/ipr/cluster/work.local/graffy/bug3958/iprbench.git$ module avail --terse
# /usr/share/modules/modulefiles:
# compilers/ifort/15.0.2
# compilers/ifort/17.0.1
# ...
# lib/mpi/intelmpi/2021.13.0
# lib/mpi/intelmpi/latest
# module-git
# module-info
# modules
# null
stdout = completed_process.stdout.decode('utf-8')
for line in stdout.splitlines():
if not re.search(r'\:$', line): # ignore the directories such as '/usr/share/modules/modulefiles:'
available_modules.add(EnvModule(line))
return available_modules
def get_module_file_path(env_module: EnvModule) -> Path:
# graffy@alambix-frontal:~$ module help compilers/ifort/latest
# -------------------------------------------------------------------
# Module Specific Help for /usr/share/modules/modulefiles/compilers/ifort/latest:
# Provides the same functionality as the command '/opt/intel/oneapi-2024.2.1/compiler/latest/env/vars.sh intel64'
# -------------------------------------------------------------------
module_file_path = None
completed_process = subprocess.run(f'module help {env_module}', executable='/bin/bash', shell=True, capture_output=True, check=True) # for some reason, module is only found when executable is /bin/bash
stdout = completed_process.stdout.decode('utf-8')
for line in stdout.splitlines():
match = re.match(r'^Module Specific Help for (?P<module_file_path>[^:]+):', line)
if match:
module_file_path = Path(match['module_file_path'])
break
if module_file_path is None:
raise ValueError(f'failed to find the file path of the environment module {env_module}')
return module_file_path

View File

@ -3,6 +3,8 @@ import subprocess
import re
from pathlib import Path
from .core import ITargetHost, PackageId, PackageVersion, HostTypeId
from .envmodules import EnvModule, get_available_modules, get_module_file_path
# import logging
DebianPackageVersion = str # a version string, as in debian package versions, eg 4:9.3.0-1ubuntu2
@ -20,7 +22,7 @@ class DebianHost(ITargetHost):
package_version = self.get_installed_package_version(package_id)
return package_version
def get_package_activation_command(self, package_id: str, package_version: str) -> str:
def get_package_activation_command(self, package_id: PackageId, package_version: str) -> str:
current_version = self.get_package_default_version(package_id)
if current_version != package_version:
raise ValueError(f'{package_id} version {package_version} not available: only {package_id} version {current_version} is available on this host')
@ -161,20 +163,49 @@ class IprClusterNode(DebianHost):
def get_host_type_id(self) -> HostTypeId:
return HostTypeId('fr.univ-rennes.ipr.cluster-node')
def get_latest_version_for_env_module(self, package_env_module: str):
def get_latest_version_for_env_module(self, package_env_module: str) -> PackageVersion:
# package_env_module: eg compilers/ifort
# graffy@alambix-frontal:~$ module help compilers/ifort/latest
# -------------------------------------------------------------------
# Module Specific Help for /usr/share/modules/modulefiles/compilers/ifort/latest:
# Provides the same functionality as the command '/opt/intel/oneapi-2024.2.1/compiler/latest/env/vars.sh intel64'
# -------------------------------------------------------------------
# graffy@alambix-frontal:~$ ls -l /usr/share/modules/modulefiles/compilers/ifort/latest
# lrwxrwxrwx 1 root root 9 18 nov. 02:11 /usr/share/modules/modulefiles/compilers/ifort/latest -> 2021.13.1
raise NotImplementedError()
def get_package_default_version(self, package_id: str) -> str:
if package_id == 'ifort':
return self.get_latest_version_for_env_module('compilers/ifort')
available_modules = get_available_modules()
shortcut_env_module = EnvModule(f'{package_env_module}/latest')
if shortcut_env_module not in available_modules:
raise ValueError(f'failed to find {shortcut_env_module} amongst available environment modules')
# logging.debug('available_modules = %s', available_modules)
shortcut_file_path = get_module_file_path(shortcut_env_module)
# logging.debug('shortcut_file_path = %s', shortcut_file_path)
assert shortcut_file_path.is_symlink(), f'unexpected case: {shortcut_file_path} is expected to be a symbolic link to an actual version of {package_env_module}'
real_file_path = shortcut_file_path.resolve() # eg /usr/share/modules/modulefiles/compilers/ifort/2021.13.1
# logging.debug('real_file_path = %s', real_file_path)
version = real_file_path.name
match = re.match(r'^[0-9]+\.[0-9]+\.[0-9]$', version)
assert match, f'unexpected format for version: {version} for module file path {real_file_path}'
return PackageVersion(version)
def get_package_default_version(self, package_id: PackageId) -> PackageVersion:
env_modules_packages = self._get_env_modules_packages()
if package_id in env_modules_packages.keys():
return self.get_latest_version_for_env_module(env_modules_packages[package_id])
else:
return super().get_package_default_version(package_id)
def get_package_activation_command(self, package_id: PackageId, package_version: str) -> str:
env_modules_packages = self._get_env_modules_packages()
if package_id in env_modules_packages.keys():
env_module = EnvModule(f'{env_modules_packages[package_id]}/{package_version}')
assert env_module in get_available_modules(), f'environment module "{env_module}" is not one of the available modules on this system'
return f'module load {env_module}'
else:
return super().get_package_activation_command(package_id, package_version)
def _get_env_modules_packages(self) -> Dict[PackageId, str]:
""" gets list packages available as environment modules"""
return {
PackageId('ifort'): 'compilers/ifort',
PackageId('intelmkl'): 'lib/mkl',
}
def _get_package_env_module_root(self, package_id: PackageId) -> str:
return self._get_env_modules_packages()[package_id]

View File

@ -1 +1 @@
__version__ = '0.0.10'
__version__ = '0.0.11'

View File

@ -6,6 +6,7 @@ import subprocess
import json
from pathlib import Path
from shutil import rmtree
from os import getenv
from iprbench.core import ResultsDbParams, HostTypeId
from cocluto.SimpaDbUtil import SshAccessedMysqlDb
@ -37,7 +38,7 @@ class ResultsDbTestCase(unittest.TestCase):
results_root_dir: Path
def setUp(self) -> None: # pylint: disable=useless-parent-delegation
self.results_root_dir = Path('/tmp/iprbenchs/test_results/resultsdb')
self.results_root_dir = Path(f'{getenv("TMPDIR", default="/tmp")}/iprbenchs/test_results/resultsdb')
if self.results_root_dir.exists():
rmtree(self.results_root_dir)
self.results_root_dir.mkdir(parents=True)