34
34
from glob import glob
35
35
from pathlib import Path
36
36
from typing import List , Optional
37
+ from contextlib import contextmanager
37
38
38
39
import mx
39
40
import mx_benchmark
@@ -561,18 +562,16 @@ class GraalOSNativeImageBenchmarkSuite(mx_benchmark.CustomHarnessBenchmarkSuite,
561
562
562
563
This benchmark suite utilizes the `graalos-load-tester` harness to execute scenarios that run workloads against
563
564
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
569
565
"""
570
566
def __init__ (self , custom_harness_command : mx_benchmark .CustomHarnessCommand = None ):
571
567
if custom_harness_command is None :
572
568
custom_harness_command = GraalOSNativeImageBenchmarkSuite .GraalOSLoadTesterCommand ()
573
569
super ().__init__ (custom_harness_command )
574
570
self ._version = None
571
+ self ._gos_scenario_home : Optional [Path ] = None
575
572
self ._deployment = None
573
+ self ._bundle_paths : dict [str , str ] = {}
574
+ self ._stage_env = os .environ .copy ()
576
575
577
576
def name (self ):
578
577
return "graalos"
@@ -592,21 +591,26 @@ def version(self):
592
591
mx .log (f"Running GraalOS Load Tester version '{ self ._version } '" )
593
592
return self ._version
594
593
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
+ """
597
607
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 ) + "\n Please 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!" )
610
614
611
615
def _read_gos_scenario_version (self ):
612
616
"""
@@ -615,7 +619,7 @@ def _read_gos_scenario_version(self):
615
619
"""
616
620
# Revisit this method once we rework versioning for graalos-load-tester (GR-59986)
617
621
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 ()
619
623
except :
620
624
return self .defaultSuiteVersion ()
621
625
@@ -625,7 +629,7 @@ def _gos_scenario_command(self) -> str:
625
629
626
630
def _gos_scenarios_dir (self ) -> Path :
627
631
"""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"
629
633
if not scenarios_dir .is_dir ():
630
634
raise ValueError (f"Directory '{ scenarios_dir } ' is supposed to contain load-testing scenarios"
631
635
f" but instead it does not exist!" )
@@ -639,12 +643,21 @@ def _app_source_dir(self, app: str) -> Path:
639
643
"""Returns the path to the source code directory of the application."""
640
644
return self ._vm_benchmarks_graalos_dir () / app / "app"
641
645
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
+ """
644
656
try :
645
- mx . run ([ self . _gos_scenario_command (), "--help" ], out = mx . OutputCapture ())
657
+ yield
646
658
except :
647
- mx .abort ("Please install the 'gos-scenario' command from the 'graalos-load-tester' repository!" )
659
+ if propagate :
660
+ raise
648
661
649
662
def completeBenchmarkList (self , bmSuiteArgs ):
650
663
return _graalosConfig ["benchmarks" ].keys ()
@@ -661,11 +674,42 @@ def benchmarkList(self, bmSuiteArgs):
661
674
# Exclude any benchmarks unsupported on the current platform, JDK version, VM
662
675
return self .completeBenchmarkList (bmSuiteArgs )
663
676
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 } \n Installing '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
+
664
710
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 ()
669
713
670
714
def new_execution_context (self , vm : Vm , benchmarks : List [str ], bmSuiteArgs : List [str ]) -> SingleBenchmarkExecutionContext :
671
715
return SingleBenchmarkExecutionContext (self , vm , benchmarks , bmSuiteArgs )
@@ -687,19 +731,52 @@ def _get_benchmark_config(self, benchmark):
687
731
return _graalosConfig ["benchmarks" ][benchmark ]
688
732
689
733
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
690
746
app_name = self ._get_benchmark_config (self .benchmarkName ())["app" ]
691
747
app_dir = self ._app_source_dir (app_name )
692
748
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 } \n Generating 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
693
774
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 } '" )
698
775
mx .abort (f"Expected to find exactly one '.nib' file in the '{ app_dir } ' app directory, instead found none!" )
699
776
if len (nib_candidates ) > 1 :
700
777
mx .abort (f"Expected to find exactly one '.nib' file in the '{ app_dir } ' app directory, instead found "
701
778
+ "multiple: [" + ", " .join (str (path ) for path in nib_candidates ) + "]" )
702
- return nib_candidates [0 ]. parent
779
+ return nib_candidates [0 ]
703
780
704
781
def uses_bundles (self ):
705
782
return True
@@ -808,6 +885,16 @@ def _get_runnable_app_image(self):
808
885
return accessible_app_image_path
809
886
return original_app_image_path
810
887
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
+
811
898
def run (self , benchmarks , bmSuiteArgs ) -> DataPoints :
812
899
return self .intercept_run (super (), benchmarks , bmSuiteArgs )
813
900
@@ -842,7 +929,7 @@ def produceHarnessCommand(self, cmd: List[str], suite: BenchmarkSuite) -> List[s
842
929
# Add explicit run stage args
843
930
app_cmd += parse_prefixed_args ("-Dnative-image.benchmark.extra-run-arg=" , bmSuiteArgs )
844
931
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" ]
846
933
timestamp = datetime .datetime .now ().strftime ("%Y-%m-%d-%H-%M-%S" )
847
934
gos_log_file_name = f"{ timestamp } -gos-out.log"
848
935
gos_cmd += ["--log-to" , f"stdout,file:{ gos_log_file_name } " ]
0 commit comments