Skip to content

Commit e747d6b

Browse files
author
Andrija Kolic
committed
Support layered benchmarks in the 'graalos' bench suite; restrict benchmarks to 'graalos-bench-suite' scenarios (rawhttp and micronaut-pegasus; round-robin and single-app); support running on the CI.
1 parent 423ad8b commit e747d6b

File tree

1 file changed

+123
-65
lines changed

1 file changed

+123
-65
lines changed

substratevm/mx.substratevm/mx_substratevm_benchmark.py

Lines changed: 123 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ def get_bundle_path_for_benchmark_layer(self, benchmark, layer_info) -> str:
340340
return str(nib_candidates[0])
341341

342342
def get_latest_layer(self) -> Optional[Layer]:
343-
latest_image_stage = self.execution_context.virtual_machine.stages_info.get_latest_image_stage()
343+
latest_image_stage = self.stages_info.get_latest_image_stage()
344344
if latest_image_stage is None or not latest_image_stage.is_layered():
345345
return None
346346
return latest_image_stage.layer_info
@@ -391,9 +391,9 @@ def ensure_image_is_at_desired_location(self, bmSuiteArgs):
391391
# we need to move the image from the path that is set inside the nib to the path expected by our vm.
392392
# This code has no effect if the image is already at the desired location.
393393
vm = self.get_vm_registry().get_vm_from_suite_args(bmSuiteArgs)
394-
if vm.stages_info.should_produce_datapoints(StageName.INSTRUMENT_IMAGE):
394+
if self.stages_info.should_produce_datapoints(StageName.INSTRUMENT_IMAGE):
395395
desired_image_path = vm.config.instrumented_image_path
396-
elif vm.stages_info.should_produce_datapoints(StageName.IMAGE):
396+
elif self.stages_info.should_produce_datapoints(StageName.IMAGE):
397397
desired_image_path = vm.config.image_path
398398
else:
399399
return
@@ -515,53 +515,55 @@ def produceHarnessCommand(self, cmd, suite):
515515
# and the scenario files (GR-65000)
516516
_graalosConfig = {
517517
"benchmarks": {
518-
"local_binary": {
519-
"app": "rawhttp-function",
520-
"scenario-path": "local_binary.toml",
521-
},
522-
"dataplane_smoke_test": {
523-
"app": "rawhttp-function",
524-
"scenario-path": "dataplane_smoke_test.toml",
525-
},
526-
"graal-ci-round-robin-rawhttp-function-256": {
527-
"app": "rawhttp-function",
528-
"scenario-path": "graal-ci/round-robin/rawhttp-function-256.toml",
529-
},
530-
"graal-ci-single-app-micronaut-pegasus-function-256": {
518+
"round-robin-micronaut-pegasus-function": {
531519
"app": "micronaut-pegasus-function",
532-
"scenario-path": "graal-ci/single-app/micronaut-pegasus-function-256.toml",
520+
"scenario-path": "graalos-bench-suite/round-robin/micronaut-pegasus-function.toml",
533521
},
534-
"graal-ci-single-app-micronaut-pegasus-function-2048": {
535-
"app": "micronaut-pegasus-function",
536-
"scenario-path": "graal-ci/single-app/micronaut-pegasus-function-2048.toml",
537-
},
538-
"graal-ci-single-app-rawhttp-function-256": {
522+
"round-robin-rawhttp-function": {
539523
"app": "rawhttp-function",
540-
"scenario-path": "graal-ci/single-app/rawhttp-function-256.toml",
524+
"scenario-path": "graalos-bench-suite/round-robin/rawhttp-function.toml",
541525
},
542-
"graal-ci-single-app-rawhttp-function-2048": {
543-
"app": "rawhttp-function",
544-
"scenario-path": "graal-ci/single-app/rawhttp-function-2048.toml",
526+
"single-app-micronaut-pegasus-function": {
527+
"app": "micronaut-pegasus-function",
528+
"scenario-path": "graalos-bench-suite/single-app/micronaut-pegasus-function.toml",
545529
},
546-
"graal-ci-smoke-test-rawhttp-function-256": {
530+
"single-app-rawhttp-function": {
547531
"app": "rawhttp-function",
548-
"scenario-path": "graal-ci/smoke-test/rawhttp-function-256.toml",
549-
},
550-
"graal-ci-stress-test-timed-compute-function-256": {
551-
"app": "timed-compute-function",
552-
"scenario-path": "graal-ci/stress-test/timed-compute-function-256.toml",
532+
"scenario-path": "graalos-bench-suite/single-app/rawhttp-function.toml",
553533
},
554534
},
555535
}
556536

557537
class GraalOSNativeImageBenchmarkSuite(mx_benchmark.CustomHarnessBenchmarkSuite,
558538
mx_sdk_benchmark.NativeImageBenchmarkMixin,
559-
mx_sdk_benchmark.NativeImageBundleBasedBenchmarkMixin):
539+
mx_sdk_benchmark.LayeredNativeImageBundleBasedBenchmarkMixin):
560540
"""
561541
A collection of benchmarks designed for benchmarking the performance of apps built for GraalOS.
562542
563543
This benchmark suite utilizes the `graalos-load-tester` harness to execute scenarios that run workloads against
564544
images of apps located in the `vm-benchmarks/graalos` repository.
545+
546+
The run arguments (arguments that are specified after the second '--' instance of the 'mx benchmark' command) are
547+
passed to the 'gos-scenario' tool. For example:
548+
```
549+
mx ... benchmark graalos:... -- ... -- -p app_count=1
550+
```
551+
will propagate '-p app_count=1' to the 'gos-scenario' harness and thus set the 'app_count' parameter of the
552+
selected scenario to 1.
553+
554+
DEVELOPER NOTE:
555+
The automatic dependency setup implemented as part of this suite does not work when multiple 'mx benchmark'
556+
processes are running concurrently. Namely, the bench suite automatically executes the following steps, if they
557+
haven't already been executed:
558+
* cloning the 'graalos-load-tester' repository as a sibling to the 'graal' repository
559+
* downloading the tools which graalos-load-tester depends on (e.g. wrk, wrk2, nginx)
560+
* generating the native image bundle from which the application image is built
561+
These steps aren't executed if:
562+
* the 'graalos-load-tester' repository is already present as a sibling to the 'graal' repository
563+
* all of the tools which graalos-load-tester depends on are installed and available on the PATH
564+
* the native image bundle for the application is present in the application source code directory
565+
Only in the case that these requirements are satisfied running concurrent 'mx benchmark' processes will not crash.
566+
(GR-66385)
565567
"""
566568
def __init__(self, custom_harness_command: mx_benchmark.CustomHarnessCommand = None):
567569
if custom_harness_command is None:
@@ -591,6 +593,19 @@ def version(self):
591593
mx.log(f"Running GraalOS Load Tester version '{self._version}'")
592594
return self._version
593595

596+
def layers(self, bm_suite_args: List[str]) -> List[Layer]:
597+
if self._get_benchmark_config(self.benchmarkName())["app"] == "micronaut-pegasus-function":
598+
return [Layer(0, True), Layer(1, False)]
599+
# Currently, "micronaut-pegasus-function" is the only app that supports running with layers
600+
# Support for other benchmarks, or even suites? (GR-64772)
601+
mx.abort(f"The '{self.benchmarkName()}' benchmark does not support layered native images!")
602+
603+
def get_latest_layer(self) -> Optional[Layer]:
604+
latest_image_stage = self.stages_info.get_latest_image_stage()
605+
if latest_image_stage is None or not latest_image_stage.is_layered():
606+
return None
607+
return latest_image_stage.layer_info
608+
594609
@property
595610
def gos_scenario_home(self) -> Path:
596611
if self._gos_scenario_home is None:
@@ -730,45 +745,77 @@ def _get_benchmark_config(self, benchmark):
730745
"""
731746
return _graalosConfig["benchmarks"][benchmark]
732747

733-
def applicationDist(self):
748+
def get_bundle_path(self) -> str:
734749
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
750+
layer_info = self.get_latest_layer()
751+
if layer_info is not None:
752+
key = f"{app_name}-layer{layer_info.index}"
753+
else:
754+
key = app_name
739755

740-
def generate_or_lookup_bundle(self) -> Path:
756+
if key not in self._bundle_paths:
757+
app_dir = self._app_source_dir(app_name)
758+
self._bundle_paths[key] = self.generate_or_lookup_bundle(app_name, layer_info, app_dir)
759+
mx.log(f"Using bundle at '{self._bundle_paths[key]}' to generate the image for '{key}'.")
760+
return self._bundle_paths[key]
761+
762+
def lookup_bundle(self, app_name: str, layer_info: Layer, app_dir: Path) -> List[Path]:
763+
"""
764+
Looks up the path to the NIB file for the app-layer pair associated with the current benchmark stage.
765+
766+
The files are searched for in the subtree of the app's root directory. All the files that match the expected
767+
naming pattern are matched. The naming pattern depends on whether the bundle has been generated for a standalone
768+
application or a single layer of a layered application build. The name of the NIB file should:
769+
* start with anything other than 'layer<NUMBER>-' if it is meant for building a standalone app image.
770+
* start with 'layer<NUMBER>-' if it is meant for building a layer, where NUMBER is the index of the layer.
771+
"""
772+
# Lookup all the NIB files located inside the subtree of the app root directory
773+
nib_candidates = list(app_dir.glob("**/*.nib"))
774+
775+
# Filter for only the NIB files that correspond to the naming scheme associated with the current layer
776+
if layer_info is None:
777+
# Select only the nib files that do not start with r'layer\d+-'
778+
nib_naming_pattern = r"^(?!layer\d+-).*\.nib$"
779+
else:
780+
# Select only the nib files that start with fr'layer{layer_info.index}-'
781+
nib_naming_pattern = fr"^layer{layer_info.index}-.*\.nib$"
782+
return [nib for nib in nib_candidates if re.match(nib_naming_pattern, nib.name)]
783+
784+
def generate_bundle(self, app_name: str, layer_info: Layer):
785+
"""Generates the NIB file for the app-layer pair associated with the current benchmark stage."""
786+
if app_name == "micronaut-pegasus-function" and mx.get_env("JDK17_HOME") is None:
787+
mx.abort(f"App '{app_name}' requires JDK17_HOME env var to point to a JDK 17 distribution in order to build maven project.")
788+
789+
nib_generation_cmd = ["./graalos-gate.py", "build", "--build-profile", "nib", app_name]
790+
if layer_info is not None:
791+
assert app_name == "micronaut-pegasus-function", f"Cannot generate a layer bundle for '{app_name}' app!"
792+
assert layer_info.index in [0, 1], f"Cannot generate layer#{layer_info.index} bundle for '{app_name}' app!"
793+
if layer_info.index == 0:
794+
nib_generation_cmd += ["--extra-build-options=--maven-options=-Pbase-layer"]
795+
else:
796+
nib_generation_cmd += ["--extra-build-options=--maven-options=-Papp-layer"]
797+
working_dir = self._vm_benchmarks_graalos_dir()
798+
mx.log(f"Generating the NIB file by running {nib_generation_cmd} in working dir '{working_dir}'. This can take a while.")
799+
try:
800+
mx.run(nib_generation_cmd, cwd=working_dir, env=self.get_nib_generation_env())
801+
except BaseException as e:
802+
if isinstance(e, SystemExit):
803+
mx.abort(f"Generating the NIB file failed with exit code {e}!")
804+
else:
805+
mx.abort(f"{e}\nGenerating the NIB file failed!")
806+
807+
def generate_or_lookup_bundle(self, app_name: str, layer_info: Layer, app_dir: Path) -> Path:
741808
"""
742809
Looks up the path to the NIB file for the app asociated with the current benchmark,
743810
generating it first if it does not exist.
744811
"""
745-
# Initial NIB lookup
746-
app_name = self._get_benchmark_config(self.benchmarkName())["app"]
747-
app_dir = self._app_source_dir(app_name)
748-
nib_candidates = list(app_dir.glob("**/*.nib"))
812+
nib_candidates = self.lookup_bundle(app_name, layer_info, app_dir)
749813

750814
# Generate a NIB file for the app if none exists
751815
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)
816+
self.generate_bundle(app_name, layer_info)
770817
# Repeat the lookup
771-
nib_candidates = list(app_dir.glob("**/*.nib"))
818+
nib_candidates = self.lookup_bundle(app_name, layer_info, app_dir)
772819

773820
# Final check
774821
if len(nib_candidates) == 0:
@@ -801,6 +848,12 @@ def extra_run_arg(self, benchmark, args, image_run_args):
801848
# Added by GraalOSLoadTesterCommand
802849
return []
803850

851+
def build_assertions(self, benchmark: str, is_gate: bool) -> List[str]:
852+
# We cannot enable assertions along with emitting a build report for layered images, due to GR-65751
853+
if self.stages_info.current_stage.is_layered:
854+
return []
855+
return super().build_assertions(benchmark, is_gate)
856+
804857
def rules(self, output, benchmarks, bmSuiteArgs) -> List[Rule]:
805858
json_file_group_name = "graalos_json_results_file_path"
806859
json_file_pattern = fr"- saved to: (?P<{json_file_group_name}>\S+?)$"
@@ -891,8 +944,13 @@ def get_stage_env(self) -> Optional[dict]:
891944
def get_nib_generation_env(self):
892945
env = self.get_stage_env().copy()
893946
graalvm_home = self.execution_context.virtual_machine.home()
894-
env["GRAALVM_HOME"] = graalvm_home
895-
env["GRADLE_CLIENT_JAVA_HOME"] = graalvm_home
947+
# graalos-gate.py (the script we use to generate the NIB files) requires these env vars
948+
# - GRAALVM_HOME so the underlying maven/gradle/custom build command can invoke native-image
949+
# - GRADLE_CLIENT_JAVA_HOME so that gradle has a JVM to execute its goals
950+
if not env.get("GRAALVM_HOME"):
951+
env["GRAALVM_HOME"] = graalvm_home
952+
if not env.get("GRADLE_CLIENT_JAVA_HOME"):
953+
env["GRADLE_CLIENT_JAVA_HOME"] = graalvm_home
896954
return env
897955

898956
def run(self, benchmarks, bmSuiteArgs) -> DataPoints:
@@ -919,7 +977,6 @@ def produceHarnessCommand(self, cmd: List[str], suite: BenchmarkSuite) -> List[s
919977
app_cmd = source_cmd_prefix
920978
app_cmd += [str(suite._get_runnable_app_image())]
921979
app_cmd += options_from_source_cmd
922-
app_cmd += suite.runArgs(bmSuiteArgs)
923980
app_cmd += parse_prefixed_args("-Dnative-image.benchmark.extra-jvm-arg=", bmSuiteArgs)
924981
if suite.stages_info.current_stage.is_instrument():
925982
# Add explicit instrument stage args
@@ -933,6 +990,7 @@ def produceHarnessCommand(self, cmd: List[str], suite: BenchmarkSuite) -> List[s
933990
timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
934991
gos_log_file_name = f"{timestamp}-gos-out.log"
935992
gos_cmd += ["--log-to", f"stdout,file:{gos_log_file_name}"]
993+
gos_cmd += suite.runArgs(bmSuiteArgs)
936994
app_cmd_str = " ".join(app_cmd)
937995
gos_cmd += ["-p", f"command=\"{app_cmd_str}\""]
938996
mx.log(f"Produced 'gos-scenario' command: '{' '.join(gos_cmd)}'")

0 commit comments

Comments
 (0)