From 4c4155ffd17494ddb2af293556b95bc0cd6dc89f Mon Sep 17 00:00:00 2001 From: Gregory Becker Date: Mon, 2 Dec 2024 17:56:13 -0800 Subject: [PATCH 1/6] name mangle ramble spack files on bootstrap --- lib/benchpark/runtime.py | 42 +++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/benchpark/runtime.py b/lib/benchpark/runtime.py index 8593703d9..6875cc8c4 100644 --- a/lib/benchpark/runtime.py +++ b/lib/benchpark/runtime.py @@ -7,6 +7,7 @@ import os import pathlib import shlex +import stat import subprocess import sys @@ -82,9 +83,28 @@ def __init__(self, dest): self.spack_location = self.dest / "spack" def bootstrap(self): + # Bootstrap Spack first because we will use Spack resources while bootstrapping ramble + if not self.spack_location.exists(): + self._install_spack() + + spack_lib_path = self.spack_location / "lib" / "spack" + externals = str(spack_lib_path / "external") + if externals not in sys.path: + sys.path.insert(1, externals) + internals = str(spack_lib_path) + if internals not in sys.path: + sys.path.insert(1, internals) + if not self.ramble_location.exists(): self._install_ramble() + ramble_lib_path = self.ramble_location / "lib" / "ramble" + + # If ramble is present but not yet name mangled, do that + ramble_spack_path = ramble_lib_path / "ramble_spack" + if not ramble_spack_path.exists(): + self._mangle_ramble() + externals = str(ramble_lib_path / "external") if externals not in sys.path: sys.path.insert(1, externals) @@ -92,11 +112,23 @@ def bootstrap(self): if internals not in sys.path: sys.path.insert(1, internals) - # Spack does not go in sys.path, but we will manually access modules from it - # The reason for this oddity is that spack modules will compete with the internal - # spack modules from ramble - if not self.spack_location.exists(): - self._install_spack() + def _mangle_ramble(self): + """Name mangle ramble spack.* symbols to allow separate spack imports.""" + import llnl.util.filesystem as fs + + files = [ + f + for f in fs.find(self.ramble_location, "*") + if os.stat(f).st_mode & stat.S_IWUSR and not os.path.isdir(f) + ] + file_filter = fs.FileFilter(*files) + # Don't replace if it's already replaced or if it's a field in an existing module + file_filter.filter(r"(? Date: Mon, 2 Dec 2024 17:56:53 -0800 Subject: [PATCH 2/6] spack imports: hacky solution no longer needed --- lib/benchpark/system.py | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/lib/benchpark/system.py b/lib/benchpark/system.py index ee527788d..27169a607 100644 --- a/lib/benchpark/system.py +++ b/lib/benchpark/system.py @@ -25,36 +25,10 @@ import ramble.config as cfg # noqa import ramble.language.language_helpers # noqa import ramble.language.shared_language # noqa -import spack.util.spack_yaml as syaml # noqa -# We cannot import this the normal way because it from modern Spack -# and mixing modern Spack modules with ramble modules that depend on -# ancient Spack will cause errors. This module is safe to load as an -# individual because it is not used by Ramble -# The following code block implements the line -# import spack.schema.packages as packages_schema -schemas = { - "spack.schema.packages": f"{bootstrapper.spack_location}/lib/spack/spack/schema/packages.py", - "spack.schema.compilers": f"{bootstrapper.spack_location}/lib/spack/spack/schema/compilers.py", -} - - -def load_schema(schema_id, schema_path): - schema_spec = importlib.util.spec_from_file_location(schema_id, schema_path) - schema = importlib.util.module_from_spec(schema_spec) - sys.modules[schema_id] = schema - schema_spec.loader.exec_module(schema) - return schema - - -packages_schema = load_schema( - "spack.schema.packages", - f"{bootstrapper.spack_location}/lib/spack/spack/schema/packages.py", -) -compilers_schema = load_schema( - "spack.schema.compilers", - f"{bootstrapper.spack_location}/lib/spack/spack/schema/compilers.py", -) +import spack.util.spack_yaml as syaml # noqa +import spack.schema.packages as packages_schema # noqa +import spack.schema.compilers as compilers_schema # noqa _repo_path = benchpark.repo.paths[benchpark.repo.ObjectTypes.systems] From 2b25c2c3d3fcd2baa44c6be44ae5975afe8f5bd4 Mon Sep 17 00:00:00 2001 From: Gregory Becker Date: Mon, 2 Dec 2024 17:57:24 -0800 Subject: [PATCH 3/6] setup: use llnl.util.link_tree instead of reimplementing --- lib/benchpark/cmd/setup.py | 50 +++++++++++++------------------------- 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/lib/benchpark/cmd/setup.py b/lib/benchpark/cmd/setup.py index 84c81a4fb..95a250f31 100644 --- a/lib/benchpark/cmd/setup.py +++ b/lib/benchpark/cmd/setup.py @@ -20,28 +20,10 @@ from benchpark.runtime import RuntimeResources import benchpark.system +bootstrapper = RuntimeResources(benchpark.paths.benchpark_home) # noqa +bootstrapper.bootstrap() # noqa -# Note: it would be nice to vendor spack.llnl.util.link_tree, but that -# involves pulling in most of llnl/util/ and spack/util/ -def symlink_tree(src, dst, include_fn=None): - """Like ``cp -R`` but instead of files, create symlinks""" - src = os.path.abspath(src) - dst = os.path.abspath(dst) - # By default, we include all filenames - include_fn = include_fn or (lambda f: True) - for x in [src, dst]: - if not os.path.isdir(x): - raise ValueError(f"Not a directory: {x}") - for src_subdir, directories, files in os.walk(src): - relative_src_dir = pathlib.Path(os.path.relpath(src_subdir, src)) - dst_dir = pathlib.Path(dst) / relative_src_dir - dst_dir.mkdir(parents=True, exist_ok=True) - for x in files: - if not include_fn(x): - continue - dst_symlink = dst_dir / x - src_file = os.path.join(src_subdir, x) - os.symlink(src_file, dst_symlink) +import llnl.util.link_tree # noqa def setup_parser(root_parser): @@ -186,23 +168,25 @@ def command(args): ramble_logs_dir.mkdir(parents=True) ramble_spack_experiment_configs_dir.mkdir(parents=True) - def include_fn(fname): + def ignore_fn(fname): # Only include .yaml files # Always exclude files that start with "." if fname.startswith("."): - return False - if fname.endswith(".yaml"): return True - return False - - symlink_tree(configs_src_dir, ramble_configs_dir, include_fn) - symlink_tree(experiment_src_dir, ramble_configs_dir, include_fn) - symlink_tree(modifier_config_dir, ramble_configs_dir, include_fn) - symlink_tree( - source_dir / "legacy" / "systems" / "common", - ramble_spack_experiment_configs_dir, - include_fn, + if fname.endswith(".yaml"): + return False + return True + + configs_tree = llnl.util.link_tree.LinkTree(configs_src_dir) + experiment_tree = llnl.util.link_tree.LinkTree(experiments_src_dir) + modifier_tree = llnl.util.link_tree.LinkTree(modifier_config_dir) + for tree in (configs_tree, experiment_tree, modifier_tree): + tree.merge(ramble_configs_dir, ignore=ignore_fn) + + systems_tree = llnl.util.link_tree.LinkTree( + source_dir / "legacy" / "systems" / "common" ) + systems_tree.merge(ramble_spack_experiment_configs_dir, ignore=ignore_fn) template_name = "execute_experiment.tpl" experiment_template_options = [ From f5b3522cbe783c5358f6e8920efeadcc461bce9f Mon Sep 17 00:00:00 2001 From: Gregory Becker Date: Tue, 3 Dec 2024 13:29:51 -0800 Subject: [PATCH 4/6] lint --- lib/benchpark/cmd/setup.py | 2 +- lib/benchpark/system.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/benchpark/cmd/setup.py b/lib/benchpark/cmd/setup.py index 95a250f31..4fda3a3c5 100644 --- a/lib/benchpark/cmd/setup.py +++ b/lib/benchpark/cmd/setup.py @@ -178,7 +178,7 @@ def ignore_fn(fname): return True configs_tree = llnl.util.link_tree.LinkTree(configs_src_dir) - experiment_tree = llnl.util.link_tree.LinkTree(experiments_src_dir) + experiment_tree = llnl.util.link_tree.LinkTree(experiment_src_dir) modifier_tree = llnl.util.link_tree.LinkTree(modifier_config_dir) for tree in (configs_tree, experiment_tree, modifier_tree): tree.merge(ramble_configs_dir, ignore=ignore_fn) diff --git a/lib/benchpark/system.py b/lib/benchpark/system.py index 27169a607..fad3b53e3 100644 --- a/lib/benchpark/system.py +++ b/lib/benchpark/system.py @@ -4,10 +4,8 @@ # SPDX-License-Identifier: Apache-2.0 import hashlib -import importlib.util import os import pathlib -import sys import yaml import benchpark.paths From 65e9100d3a7fac68beb33d31e84ecd06ecf44da0 Mon Sep 17 00:00:00 2001 From: Gregory Becker Date: Wed, 9 Oct 2024 10:30:41 -0700 Subject: [PATCH 5/6] cmd/init: command to initialize one system and any number of experiments --- lib/benchpark/cmd/init.py | 77 +++++++++++++++++++++++++++++++++++++++ lib/main.py | 7 ++++ 2 files changed, 84 insertions(+) create mode 100644 lib/benchpark/cmd/init.py diff --git a/lib/benchpark/cmd/init.py b/lib/benchpark/cmd/init.py new file mode 100644 index 000000000..ab9af501a --- /dev/null +++ b/lib/benchpark/cmd/init.py @@ -0,0 +1,77 @@ +# Copyright 2023 Lawrence Livermore National Security, LLC and other +# Benchpark Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import os +import shutil +import sys + +import benchpark.experiment +import benchpark.spec + + +def init(args): + # Handle conjoined arguments that argparse doesn't separate + specs_str = " ".join(args.specs) + experiment_args, system_args = specs_str.split("--") + + # Parse and concretize system + system_spec = benchpark.spec.SystemSpec(system_args).concretize() + system = system_spec.system + + # Parse and concretize experiments + exp_spec_parser = benchpark.spec.SpecParser( + benchpark.spec.ExperimentSpec, experiment_args + ) + experiment_specs = [es.concretize() for es in exp_spec_parser.all_specs()] + experiments = [es.experiment for es in experiment_specs] + + # Create system directory and print out yaml + sysdir = os.path.join(args.basedir, system.system_uid()) + + try: + os.mkdir(sysdir) + system.generate_description(sysdir) + except FileExistsError: + print(f"Abort: system dir already exists ({sysdir})") + sys.exit(1) + except Exception: + # If there was a failure, remove any partially-generated resources + shutil.rmtree(sysdir) + raise + + # For each experiment, create experiment directory and print out yaml + for experiment_spec, experiment in zip(experiment_specs, experiments): + expdir = os.path.join(args.basedir, str(hash(experiment_spec))) + + try: + os.mkdir(expdir) + experiment.write_ramble_dict(f"{expdir}/ramble.yaml") + except FileExistsError: + print(f"Abort: workload dir already exists ({expdir})") + sys.exit(1) + except Exception: + # If there was a failure, remove any partially-generated resources + shutil.rmtree(expdir) + raise + + +def setup_parser(parser): + parser.add_argument( + "--basedir", + required=True, + help="Generate a system dir under this, and place all files there", + ) + + parser.add_argument( + "specs", + nargs=argparse.REMAINDER, + metavar="experiment_spec(s) -- system_spec", + help="Experiment spec(s) and system spec", + ) + + +def command(args): + init(args) diff --git a/lib/main.py b/lib/main.py index bb8e071c1..2d514bc86 100755 --- a/lib/main.py +++ b/lib/main.py @@ -17,6 +17,7 @@ import benchpark.cmd.audit import benchpark.cmd.system import benchpark.cmd.experiment +import benchpark.cmd.init import benchpark.cmd.setup import benchpark.cmd.unit_test import benchpark.paths @@ -198,6 +199,11 @@ def init_commands(subparsers, actions_dict): system_parser = subparsers.add_parser("system", help="Initialize a system config") benchpark.cmd.system.setup_parser(system_parser) + init_parser = subparsers.add_parser( + "init", help="Initialize a set of experiments with a compatible system" + ) + benchpark.cmd.init.setup_parser(init_parser) + experiment_parser = subparsers.add_parser( "experiment", help="Interact with experiments" ) @@ -218,6 +224,7 @@ def init_commands(subparsers, actions_dict): ) benchpark.cmd.audit.setup_parser(audit_parser) + actions_dict["init"] = benchpark.cmd.init.command actions_dict["system"] = benchpark.cmd.system.command actions_dict["experiment"] = benchpark.cmd.experiment.command actions_dict["setup"] = benchpark.cmd.setup.command From a49e3c5e935bae4ae23359818a748583fa6bdd2b Mon Sep 17 00:00:00 2001 From: Gregory Becker Date: Tue, 3 Dec 2024 14:55:37 -0800 Subject: [PATCH 6/6] Revert "setup: use llnl.util.link_tree instead of reimplementing" This reverts commit 2b25c2c3d3fcd2baa44c6be44ae5975afe8f5bd4. --- lib/benchpark/cmd/setup.py | 50 +++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/lib/benchpark/cmd/setup.py b/lib/benchpark/cmd/setup.py index 4fda3a3c5..84c81a4fb 100644 --- a/lib/benchpark/cmd/setup.py +++ b/lib/benchpark/cmd/setup.py @@ -20,10 +20,28 @@ from benchpark.runtime import RuntimeResources import benchpark.system -bootstrapper = RuntimeResources(benchpark.paths.benchpark_home) # noqa -bootstrapper.bootstrap() # noqa -import llnl.util.link_tree # noqa +# Note: it would be nice to vendor spack.llnl.util.link_tree, but that +# involves pulling in most of llnl/util/ and spack/util/ +def symlink_tree(src, dst, include_fn=None): + """Like ``cp -R`` but instead of files, create symlinks""" + src = os.path.abspath(src) + dst = os.path.abspath(dst) + # By default, we include all filenames + include_fn = include_fn or (lambda f: True) + for x in [src, dst]: + if not os.path.isdir(x): + raise ValueError(f"Not a directory: {x}") + for src_subdir, directories, files in os.walk(src): + relative_src_dir = pathlib.Path(os.path.relpath(src_subdir, src)) + dst_dir = pathlib.Path(dst) / relative_src_dir + dst_dir.mkdir(parents=True, exist_ok=True) + for x in files: + if not include_fn(x): + continue + dst_symlink = dst_dir / x + src_file = os.path.join(src_subdir, x) + os.symlink(src_file, dst_symlink) def setup_parser(root_parser): @@ -168,25 +186,23 @@ def command(args): ramble_logs_dir.mkdir(parents=True) ramble_spack_experiment_configs_dir.mkdir(parents=True) - def ignore_fn(fname): + def include_fn(fname): # Only include .yaml files # Always exclude files that start with "." if fname.startswith("."): - return True - if fname.endswith(".yaml"): return False - return True - - configs_tree = llnl.util.link_tree.LinkTree(configs_src_dir) - experiment_tree = llnl.util.link_tree.LinkTree(experiment_src_dir) - modifier_tree = llnl.util.link_tree.LinkTree(modifier_config_dir) - for tree in (configs_tree, experiment_tree, modifier_tree): - tree.merge(ramble_configs_dir, ignore=ignore_fn) - - systems_tree = llnl.util.link_tree.LinkTree( - source_dir / "legacy" / "systems" / "common" + if fname.endswith(".yaml"): + return True + return False + + symlink_tree(configs_src_dir, ramble_configs_dir, include_fn) + symlink_tree(experiment_src_dir, ramble_configs_dir, include_fn) + symlink_tree(modifier_config_dir, ramble_configs_dir, include_fn) + symlink_tree( + source_dir / "legacy" / "systems" / "common", + ramble_spack_experiment_configs_dir, + include_fn, ) - systems_tree.merge(ramble_spack_experiment_configs_dir, ignore=ignore_fn) template_name = "execute_experiment.tpl" experiment_template_options = [