Skip to content

Commit 84eee1d

Browse files
author
Andrija Kolic
committed
[GR-65027] Run 'graalos' suite in one command
PullRequest: graal/20937
2 parents 5d6e84b + 31e985f commit 84eee1d

File tree

4 files changed

+129
-38
lines changed

4 files changed

+129
-38
lines changed

common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"Jsonnet files should not include this file directly but use ci/common.jsonnet instead."
55
],
66

7-
"mx_version": "7.55.6",
7+
"mx_version": "7.55.7",
88

99
"COMMENT.jdks": "When adding or removing JDKs keep in sync with JDKs in ci/common.jsonnet",
1010
"jdks": {

sdk/mx.sdk/mx_sdk_benchmark.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4003,7 +4003,7 @@ def run_stage(self, vm, stage: Stage, command, out, err, cwd, nonZeroIsFatal):
40034003
# produced in the corresponding run stages.
40044004
if not stage.is_image() and self.stages_info.should_produce_datapoints(stage.stage_name):
40054005
final_command = self.apply_command_mapper_hooks(command, vm)
4006-
return mx.run(final_command, out=out, err=err, cwd=cwd, nonZeroIsFatal=nonZeroIsFatal)
4006+
return mx.run(final_command, out=out, err=err, cwd=cwd, nonZeroIsFatal=nonZeroIsFatal, env=self.get_stage_env())
40074007

40084008
def is_native_mode(self, bm_suite_args: List[str]):
40094009
"""Checks whether the given arguments request a Native Image benchmark"""
@@ -4139,6 +4139,10 @@ def build_assertions(self, benchmark, is_gate):
41394139
def checkSamplesInPgo(self):
41404140
return True
41414141

4142+
def get_stage_env(self) -> Optional[dict]:
4143+
"""Return the environment to be used when executing a stage."""
4144+
return None
4145+
41424146

41434147
def measureTimeToFirstResponse(bmSuite):
41444148
protocolHost = bmSuite.serviceHost()

substratevm/mx.substratevm/mx_substratevm_benchmark.py

Lines changed: 122 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from glob import glob
3535
from pathlib import Path
3636
from typing import List, Optional
37+
from contextlib import contextmanager
3738

3839
import mx
3940
import mx_benchmark
@@ -561,18 +562,16 @@ class GraalOSNativeImageBenchmarkSuite(mx_benchmark.CustomHarnessBenchmarkSuite,
561562
562563
This benchmark suite utilizes the `graalos-load-tester` harness to execute scenarios that run workloads against
563564
images of apps located in the `vm-benchmarks/graalos` repository.
564-
565-
Running a benchmark from this suite requires the following prerequisites:
566-
* the `graalos-load-tester` repository is cloned, installed, and all of it's dependencies are installed
567-
* the GOS_SCENARIO_HOME environment variable is set to point to the `graalos-load-tester` cloned repo
568-
* the NIB (Native Image Bundle) has been generated for the application that comprises the benchmark
569565
"""
570566
def __init__(self, custom_harness_command: mx_benchmark.CustomHarnessCommand = None):
571567
if custom_harness_command is None:
572568
custom_harness_command = GraalOSNativeImageBenchmarkSuite.GraalOSLoadTesterCommand()
573569
super().__init__(custom_harness_command)
574570
self._version = None
571+
self._gos_scenario_home: Optional[Path] = None
575572
self._deployment = None
573+
self._bundle_paths: dict[str, str] = {}
574+
self._stage_env = os.environ.copy()
576575

577576
def name(self):
578577
return "graalos"
@@ -592,21 +591,26 @@ def version(self):
592591
mx.log(f"Running GraalOS Load Tester version '{self._version}'")
593592
return self._version
594593

595-
def _gos_scenario_home(self) -> Path:
596-
"""Verifies that the GOS_SCENARIO_HOME env var points to a directory and then returns the path to it."""
594+
@property
595+
def gos_scenario_home(self) -> Path:
596+
if self._gos_scenario_home is None:
597+
self._gos_scenario_home = self._load_gos_scenario_home()
598+
return self._gos_scenario_home
599+
600+
def _load_gos_scenario_home(self) -> Path:
601+
"""
602+
Returns the path to the 'graalos-load-tester' directory sibling to the root 'graal' directory,
603+
cloning the remote repository if it is missing.
604+
605+
:raises StopIteration: If 'graalos-load-tester' is not found in the list of ignored suites.
606+
"""
597607
try:
598-
gos_scenario_home_env_var = mx.get_env("GOS_SCENARIO_HOME")
599-
if gos_scenario_home_env_var is None:
600-
raise ValueError("GOS_SCENARIO_HOME is not set!")
601-
gos_scenario_home = Path(gos_scenario_home_env_var)
602-
if not gos_scenario_home.is_dir():
603-
raise ValueError("GOS_SCENARIO_HOME does not point to an existing directory!")
604-
return gos_scenario_home
605-
except ValueError as e:
606-
mx.abort(
607-
str(e) + "\nPlease set the GOS_SCENARIO_HOME environment variable to point"
608-
" to a copy of the 'graalos-load-tester' repository."
609-
)
608+
glt_suite = mx.suite("graalos-load-tester", fatalIfMissing=False)
609+
if glt_suite is None:
610+
glt_suite = mx.primary_suite().clone_foreign_suite("graalos-load-tester", clone_binary_first=False)
611+
return Path(glt_suite.dir)
612+
except StopIteration:
613+
mx.abort("Cloning of 'graalos-load-tester' as a sibling of the current suite has failed!")
610614

611615
def _read_gos_scenario_version(self):
612616
"""
@@ -615,7 +619,7 @@ def _read_gos_scenario_version(self):
615619
"""
616620
# Revisit this method once we rework versioning for graalos-load-tester (GR-59986)
617621
try:
618-
return mx.GitConfig().git_command(self._gos_scenario_home(), ["describe", "--tags", "--abbrev=0"]).strip()
622+
return mx.GitConfig().git_command(self.gos_scenario_home, ["describe", "--tags", "--abbrev=0"]).strip()
619623
except:
620624
return self.defaultSuiteVersion()
621625

@@ -625,7 +629,7 @@ def _gos_scenario_command(self) -> str:
625629

626630
def _gos_scenarios_dir(self) -> Path:
627631
"""Verifies that the root scenarios directory exists and returns the path to it."""
628-
scenarios_dir = self._gos_scenario_home() / "scenarios"
632+
scenarios_dir = self.gos_scenario_home / "scenarios"
629633
if not scenarios_dir.is_dir():
630634
raise ValueError(f"Directory '{scenarios_dir}' is supposed to contain load-testing scenarios"
631635
f" but instead it does not exist!")
@@ -639,12 +643,21 @@ def _app_source_dir(self, app: str) -> Path:
639643
"""Returns the path to the source code directory of the application."""
640644
return self._vm_benchmarks_graalos_dir() / app / "app"
641645

642-
def _check_if_gos_scenario_command_is_installed(self):
643-
"""Verifies that the command that executes the `graalos-load-tester` benchmarking harness is installed."""
646+
@contextmanager
647+
def catch_all_errors(self, propagate):
648+
"""
649+
Context manager that catches any error raised by the managed code and either suppresses or propagates it.
650+
651+
The error is either propagated or suppressed based on the value of the `propagate` parameter:
652+
* If `propagate` is `True` then any error raised by the managed code is propagated - the behaviour is as if
653+
the manager was absent.
654+
* If `propagate` is `False` then any error raised by the managed code is suppressed.
655+
"""
644656
try:
645-
mx.run([self._gos_scenario_command(), "--help"], out=mx.OutputCapture())
657+
yield
646658
except:
647-
mx.abort("Please install the 'gos-scenario' command from the 'graalos-load-tester' repository!")
659+
if propagate:
660+
raise
648661

649662
def completeBenchmarkList(self, bmSuiteArgs):
650663
return _graalosConfig["benchmarks"].keys()
@@ -661,11 +674,42 @@ def benchmarkList(self, bmSuiteArgs):
661674
# Exclude any benchmarks unsupported on the current platform, JDK version, VM
662675
return self.completeBenchmarkList(bmSuiteArgs)
663676

677+
def _install_graalos_load_tester(self):
678+
"""
679+
Installs the 'graalos-load-tester' project and its dependencies and modifies the PATH
680+
environment variable inside `_stage_env` to include paths emitted by the installation script.
681+
"""
682+
install_script_path = self.gos_scenario_home / "mx-devenv" / "local-install.py"
683+
install_cmd = [str(install_script_path)]
684+
685+
mx.log(f"Installing `graalos-load-tester' with: {install_cmd}")
686+
out = mx.OutputCapture()
687+
err = mx.OutputCapture()
688+
try:
689+
mx.run(install_cmd, out=out, err=err)
690+
except BaseException as e:
691+
for line in out.data.split("\n"):
692+
mx.log(line)
693+
for line in err.data.split("\n"):
694+
mx.log_error(line)
695+
if isinstance(e, SystemExit):
696+
mx.abort(f"Installing 'graalos-load-tester' failed with exit code {e}!")
697+
else:
698+
mx.abort(f"{e}\nInstalling 'graalos-load-tester' failed!")
699+
700+
for line in out.data.split("\n"):
701+
mx.log(line)
702+
path_env_var_pattern = r"^.*export PATH=(.*):\$PATH$"
703+
for line in out.data.split("\n"):
704+
path_entry_match = re.match(path_env_var_pattern, line)
705+
if path_entry_match:
706+
new_path_entry = path_entry_match.group(1)
707+
mx.log(f"Prepending '{new_path_entry}' to the PATH environment variable for the duration of the benchmark.")
708+
self._stage_env["PATH"] = f"{new_path_entry}:{self._stage_env['PATH']}"
709+
664710
def validateEnvironment(self):
665-
# Verify GOS_SCENARIO_HOME env var is set
666-
self._gos_scenario_home()
667-
# Verify 'gos-scenario' command is installed
668-
self._check_if_gos_scenario_command_is_installed()
711+
# Make sure 'graalos-load-tester' repo is present and dependencies are installed
712+
self._install_graalos_load_tester()
669713

670714
def new_execution_context(self, vm: Vm, benchmarks: List[str], bmSuiteArgs: List[str]) -> SingleBenchmarkExecutionContext:
671715
return SingleBenchmarkExecutionContext(self, vm, benchmarks, bmSuiteArgs)
@@ -687,19 +731,52 @@ def _get_benchmark_config(self, benchmark):
687731
return _graalosConfig["benchmarks"][benchmark]
688732

689733
def applicationDist(self):
734+
app_name = self._get_benchmark_config(self.benchmarkName())["app"]
735+
if app_name not in self._bundle_paths:
736+
self._bundle_paths[app_name] = self.generate_or_lookup_bundle()
737+
mx.log(f"Using bundle at '{self._bundle_paths[app_name]}' for app '{app_name}'.")
738+
return self._bundle_paths[app_name].parent
739+
740+
def generate_or_lookup_bundle(self) -> Path:
741+
"""
742+
Looks up the path to the NIB file for the app asociated with the current benchmark,
743+
generating it first if it does not exist.
744+
"""
745+
# Initial NIB lookup
690746
app_name = self._get_benchmark_config(self.benchmarkName())["app"]
691747
app_dir = self._app_source_dir(app_name)
692748
nib_candidates = list(app_dir.glob("**/*.nib"))
749+
750+
# Generate a NIB file for the app if none exists
751+
if len(nib_candidates) == 0:
752+
nib_generation_cmd = ["./graalos-gate.py", "build", "--build-profile", "nib", app_name]
753+
working_dir = self._vm_benchmarks_graalos_dir()
754+
mx.log(f"Generating the NIB file by running {nib_generation_cmd} in working dir {working_dir}")
755+
out = mx.OutputCapture()
756+
err = mx.OutputCapture()
757+
try:
758+
mx.run(nib_generation_cmd, cwd=working_dir, out=out, err=err, env=self.get_nib_generation_env())
759+
except BaseException as e:
760+
for line in out.data.split("\n"):
761+
mx.log(line)
762+
for line in err.data.split("\n"):
763+
mx.log_error(line)
764+
if isinstance(e, SystemExit):
765+
mx.abort(f"Generating the NIB file failed with exit code {e}!")
766+
else:
767+
mx.abort(f"{e}\nGenerating the NIB file failed!")
768+
for line in out.data.split("\n"):
769+
mx.logvv(line)
770+
# Repeat the lookup
771+
nib_candidates = list(app_dir.glob("**/*.nib"))
772+
773+
# Final check
693774
if len(nib_candidates) == 0:
694-
build_cmd_to_run = [f"{self._vm_benchmarks_graalos_dir()}/graalos-gate.py",
695-
"build", "--build-profile", "nib", app_name]
696-
build_cmd_to_run = " ".join(build_cmd_to_run)
697-
mx.log_error(f"Did you forget to run: '{build_cmd_to_run}'")
698775
mx.abort(f"Expected to find exactly one '.nib' file in the '{app_dir}' app directory, instead found none!")
699776
if len(nib_candidates) > 1:
700777
mx.abort(f"Expected to find exactly one '.nib' file in the '{app_dir}' app directory, instead found "
701778
+ "multiple: [" + ", ".join(str(path) for path in nib_candidates) + "]")
702-
return nib_candidates[0].parent
779+
return nib_candidates[0]
703780

704781
def uses_bundles(self):
705782
return True
@@ -808,6 +885,16 @@ def _get_runnable_app_image(self):
808885
return accessible_app_image_path
809886
return original_app_image_path
810887

888+
def get_stage_env(self) -> Optional[dict]:
889+
return self._stage_env
890+
891+
def get_nib_generation_env(self):
892+
env = self.get_stage_env().copy()
893+
graalvm_home = self.execution_context.virtual_machine.home()
894+
env["GRAALVM_HOME"] = graalvm_home
895+
env["GRADLE_CLIENT_JAVA_HOME"] = graalvm_home
896+
return env
897+
811898
def run(self, benchmarks, bmSuiteArgs) -> DataPoints:
812899
return self.intercept_run(super(), benchmarks, bmSuiteArgs)
813900

@@ -842,7 +929,7 @@ def produceHarnessCommand(self, cmd: List[str], suite: BenchmarkSuite) -> List[s
842929
# Add explicit run stage args
843930
app_cmd += parse_prefixed_args("-Dnative-image.benchmark.extra-run-arg=", bmSuiteArgs)
844931

845-
gos_cmd = ["gos-scenario", f"{scenario}", "--local-load-testers", "--skip-upload"]
932+
gos_cmd = [suite._gos_scenario_command(), f"{scenario}", "--local-load-testers", "--skip-upload"]
846933
timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
847934
gos_log_file_name = f"{timestamp}-gos-out.log"
848935
gos_cmd += ["--log-to", f"stdout,file:{gos_log_file_name}"]

substratevm/mx.substratevm/suite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pylint: disable=line-too-long
22
suite = {
3-
"mxversion": "7.55.2",
3+
"mxversion": "7.55.7",
44
"name": "substratevm",
55
"version" : "26.0.0",
66
"release" : False,

0 commit comments

Comments
 (0)