5454import argparse
5555import zipfile
5656from dataclasses import dataclass
57+ from enum import Enum
5758from os import PathLike
5859from os .path import exists , basename
5960from pathlib import Path
@@ -302,9 +303,9 @@ def __init__(self, vm: NativeImageVM, bm_suite: BenchmarkSuite | NativeImageBenc
302303 for option in self .extra_agentlib_options :
303304 if option .startswith ('config-output-dir' ):
304305 mx .abort ("config-output-dir must not be set in the extra_agentlib_options." )
305- # Do not strip the run arguments if safepoint-sampler or pgo_sampler_only configuration is active
306+ # Do not strip the run arguments if safepoint-sampler configuration is active or we want pgo samples (either from instrumentation or perf)
306307 self .extra_profile_run_args = bm_suite .extra_profile_run_arg (self .benchmark_name , args , list (image_run_args ),
307- not (vm .safepoint_sampler or vm .pgo_sampler_only ))
308+ not (vm .safepoint_sampler or vm .pgo_sampler_only or vm . pgo_use_perf ))
308309 self .extra_agent_profile_run_args = bm_suite .extra_agent_profile_run_arg (self .benchmark_name , args ,
309310 list (image_run_args ))
310311 self .params = ['extra-image-build-argument' , 'extra-jvm-arg' , 'extra-run-arg' , 'extra-agent-run-arg' ,
@@ -320,10 +321,16 @@ def __init__(self, vm: NativeImageVM, bm_suite: BenchmarkSuite | NativeImageBenc
320321 self .output_dir : Path = output_dir
321322 self .final_image_name = self .executable_name + '-' + vm .config_name ()
322323 self .profile_path : Path = self .output_dir / f"{ self .executable_name } .iprof"
324+ self .source_mappings_path : Path = self .output_dir / f"{ self .executable_name } .sourceMappings.json"
325+ self .perf_script_path : Path = self .output_dir / f"{ self .executable_name } .perf.script.out"
326+ self .perf_data_path : Path = self .output_dir / f"{ self .executable_name } .perf.data"
323327 self .config_dir : Path = self .output_dir / "config"
324328 self .log_dir : Path = self .output_dir
325329 self .ml_log_dump_path : Path = self .output_dir / f"{ self .executable_name } .ml.log.csv"
326- base_image_build_args = ['--no-fallback' , '-g' ]
330+ base_image_build_args = ['--no-fallback' ]
331+ if not vm .pgo_use_perf :
332+ # Can only have debug info when not using perf, [GR-66850]
333+ base_image_build_args .append ('-g' )
327334 base_image_build_args += ['-H:+VerifyGraalGraphs' , '-H:+VerifyPhases' ,
328335 '--diagnostics-mode' ] if vm .is_gate else []
329336 base_image_build_args += ['-H:+ReportExceptionStackTraces' ]
@@ -712,9 +719,13 @@ class NativeImageVM(GraalVm):
712719 def __init__ (self , name , config_name , extra_java_args = None , extra_launcher_args = None ):
713720 super ().__init__ (name , config_name , extra_java_args , extra_launcher_args )
714721 self .vm_args = None
722+ # When this is set, run the instrumentation-image and instrumentation-run stages.
723+ # Does not necessarily do instrumentation.
715724 self .pgo_instrumentation = False
716725 self .pgo_exclude_conditional = False
717726 self .pgo_sampler_only = False
727+ self .pgo_use_perf = False
728+ self .pgo_perf_invoke_profile_collection_strategy : Optional [PerfInvokeProfileCollectionStrategy ] = None
718729 self .is_gate = False
719730 self .is_quickbuild = False
720731 self .layered = False
@@ -803,6 +814,10 @@ def config_name(self):
803814 and self .force_profile_inference is False \
804815 and self .profile_inference_feature_extraction is False :
805816 config += ["pgo" ]
817+ if self .pgo_use_perf :
818+ config += ["perf-sampler" ]
819+ if self .pgo_perf_invoke_profile_collection_strategy is not None :
820+ config += [str (self .pgo_perf_invoke_profile_collection_strategy )]
806821 if self .analysis_context_sensitivity is not None :
807822 sensitivity = self .analysis_context_sensitivity
808823 if sensitivity .startswith ("_" ):
@@ -848,7 +863,7 @@ def _configure_from_name(self, config_name):
848863 # Note: the order of entries here must match the order of statements in NativeImageVM.config_name()
849864 rule = r'^(?P<native_architecture>native-architecture-)?(?P<string_inlining>string-inlining-)?(?P<otw>otw-)?(?P<compacting_gc>compacting-gc-)?(?P<preserve_all>preserve-all-)?(?P<preserve_classpath>preserve-classpath-)?' \
850865 r'(?P<future_defaults_all>future-defaults-all-)?(?P<gate>gate-)?(?P<upx>upx-)?(?P<quickbuild>quickbuild-)?(?P<layered>layered-)?(?P<graalos>graalos-)?(?P<gc>g1gc-)?' \
851- r'(?P<llvm>llvm-)?(?P<pgo>pgo-|pgo-sampler-)?(?P<inliner>inline-)?' \
866+ r'(?P<llvm>llvm-)?(?P<pgo>pgo-|pgo-sampler-|pgo-perf-sampler-invoke-multiple-|pgo-perf-sampler-invoke-|pgo-perf-sampler- )?(?P<inliner>inline-)?' \
852867 r'(?P<analysis_context_sensitivity>insens-|allocsens-|1obj-|2obj1h-|3obj2h-|4obj3h-)?(?P<jdk_profiles>jdk-profiles-collect-|adopted-jdk-pgo-)?' \
853868 r'(?P<profile_inference>profile-inference-feature-extraction-|profile-inference-call-count-|profile-inference-pgo-|profile-inference-debug-)?(?P<sampler>safepoint-sampler-|async-sampler-)?(?P<optimization_level>O0-|O1-|O2-|O3-|Os-)?(default-)?(?P<edition>ce-|ee-)?$'
854869
@@ -926,6 +941,17 @@ def _configure_from_name(self, config_name):
926941 elif pgo_mode == "pgo-sampler" :
927942 self .pgo_instrumentation = True
928943 self .pgo_sampler_only = True
944+ elif pgo_mode == "pgo-perf-sampler" :
945+ self .pgo_instrumentation = True
946+ self .pgo_use_perf = True
947+ elif pgo_mode == "pgo-perf-sampler-invoke" :
948+ self .pgo_instrumentation = True
949+ self .pgo_use_perf = True
950+ self .pgo_perf_invoke_profile_collection_strategy = PerfInvokeProfileCollectionStrategy .ALL
951+ elif pgo_mode == "pgo-perf-sampler-invoke-multiple" :
952+ self .pgo_instrumentation = True
953+ self .pgo_use_perf = True
954+ self .pgo_perf_invoke_profile_collection_strategy = PerfInvokeProfileCollectionStrategy .MULTIPLE_CALLEES
929955 else :
930956 mx .abort (f"Unknown pgo mode: { pgo_mode } " )
931957
@@ -1330,8 +1356,11 @@ def image_build_statistics_rules(self, benchmarks):
13301356 return rules
13311357
13321358 def image_build_timers_rules (self , benchmarks ):
1333- measured_phases = ['total' , 'setup' , 'classlist' , 'analysis' , 'universe' , 'compile' , 'layout' , 'dbginfo' ,
1359+ measured_phases = ['total' , 'setup' , 'classlist' , 'analysis' , 'universe' , 'compile' , 'layout' ,
13341360 'image' , 'write' ]
1361+ if not self .pgo_use_perf :
1362+ # No debug info with perf, [GR-66850]
1363+ measured_phases .append ('dbginfo' )
13351364 rules = []
13361365 for i in range (0 , len (measured_phases )):
13371366 phase = measured_phases [i ]
@@ -1434,8 +1463,12 @@ def run_stage_agent(self):
14341463
14351464 def run_stage_instrument_image (self ):
14361465 executable_name_args = ['-o' , str (self .config .instrumented_image_path )]
1437- instrument_args = ['--pgo-sampling' ] if self .pgo_sampler_only else ['--pgo-instrument' ]
1438- instrument_args += [f"-R:ProfilesDumpFile={ self .config .profile_path } " ]
1466+ instrument_args = []
1467+ if self .pgo_use_perf :
1468+ instrument_args += svm_experimental_options ([f'-H:PGOPerfSourceMappings={ self .config .source_mappings_path } ' ])
1469+ else :
1470+ instrument_args += ['--pgo-sampling' if self .pgo_sampler_only else '--pgo-instrument' , f"-R:ProfilesDumpFile={ self .config .profile_path } " ]
1471+
14391472 if self .jdk_profiles_collect :
14401473 instrument_args += svm_experimental_options (['-H:+AOTPriorityInline' , '-H:-SamplingCollect' ,
14411474 f'-H:ProfilingPackagePrefixes={ self .generate_profiling_package_prefixes ()} ' ])
@@ -1484,13 +1517,48 @@ def _ensureSamplesAreInProfile(self, profile_path: PathLike):
14841517 assert sample ["records" ][
14851518 0 ] > 0 , f"Sampling profiles seem to have a 0 in records in file { profile_path } "
14861519
1520+ def _collect_perf_results_into_iprof (self ):
1521+ with open (self .config .perf_script_path , 'w' ) as outfile :
1522+ mx .log (f"Started perf script at { self .get_stage_runner ().get_timestamp ()} " )
1523+ exit_code = mx .run (['perf' , 'script' , f'--input={ self .config .perf_data_path } ' , '--max-stack=2048' ], out = outfile )
1524+ if exit_code == 0 :
1525+ mx .log (f"Finished perf script at { self .get_stage_runner ().get_timestamp ()} " )
1526+ mx .log (f"Perf compressed data file size: { os .path .getsize (self .config .perf_data_path )} bytes" )
1527+ mx .log (f"Perf script file size: { os .path .getsize (self .config .perf_script_path )} bytes" )
1528+ else :
1529+ mx .abort (f"Perf script failed with exit code: { exit_code } " )
1530+ mx .log (f"Started generating iprof at { self .get_stage_runner ().get_timestamp ()} " )
1531+ nic_command = [os .path .join (self .home (), 'bin' , 'native-image-configure' ), 'generate-iprof-from-perf' , f'--perf={ self .config .perf_script_path } ' , f'--source-mappings={ self .config .source_mappings_path } ' , f'--output-file={ self .config .profile_path } ' ]
1532+ if self .pgo_perf_invoke_profile_collection_strategy == PerfInvokeProfileCollectionStrategy .ALL :
1533+ nic_command += ["--enable-experimental-option=SampledVirtualInvokeProfilesAll" ]
1534+ elif self .pgo_perf_invoke_profile_collection_strategy == PerfInvokeProfileCollectionStrategy .MULTIPLE_CALLEES :
1535+ nic_command += ["--enable-experimental-option=SampledVirtualInvokeProfilesMultipleCallees" ]
1536+
1537+ mx .run (nic_command )
1538+ mx .log (f"Finished generating iprof at { self .get_stage_runner ().get_timestamp ()} " )
1539+ os .remove (self .config .perf_script_path )
1540+ os .remove (self .config .perf_data_path )
1541+ os .remove (self .config .source_mappings_path )
1542+
14871543 def run_stage_instrument_run (self ):
14881544 image_run_cmd = [str (self .config .instrumented_image_path )]
14891545 image_run_cmd += self .config .extra_jvm_args
14901546 image_run_cmd += self .config .extra_profile_run_args
1547+ if self .pgo_use_perf :
1548+ image_run_cmd = ['perf' , 'record' , '-o' , f'{ self .config .perf_data_path } ' , '--call-graph' , 'fp,2048' , '--freq=999' ] + image_run_cmd
1549+
14911550 with self .get_stage_runner () as s :
1492- exit_code = s .execute_command (self , image_run_cmd )
1551+ if self .pgo_use_perf :
1552+ mx .log (f"Started perf record at { self .get_stage_runner ().get_timestamp ()} " )
1553+ exit_code = s .execute_command (self , image_run_cmd )
1554+ mx .log (f"Finished perf record at { self .get_stage_runner ().get_timestamp ()} " )
1555+ else :
1556+ exit_code = s .execute_command (self , image_run_cmd )
1557+
14931558 if exit_code == 0 :
1559+ if self .pgo_use_perf :
1560+ self ._collect_perf_results_into_iprof ()
1561+
14941562 if not self .config .profile_path .exists ():
14951563 # The shutdown hook does not trigger for certain apps (GR-60456)
14961564 mx .abort (
@@ -1543,6 +1611,13 @@ def get_layered_build_args(self) -> List[str]:
15431611 def run_stage_image (self ):
15441612 executable_name_args = ['-o' , self .config .final_image_name ]
15451613 pgo_args = [f"--pgo={ self .config .profile_path } " ]
1614+ if self .pgo_use_perf :
1615+ # -g is already set in base_image_build_args if we're not using perf. When using perf, if debug symbols
1616+ # are present they will interfere with sample decoding using source mappings.
1617+ # We still set -g for the optimized build to stay consistent with the other configs.
1618+ # [GR-66850] would allow enabling -g during instrument-image even with perf.
1619+ executable_name_args = ['-g' ] + executable_name_args
1620+ pgo_args += svm_experimental_options (['-H:+PGOPrintProfileQuality' , '-H:+PGOIgnoreVersionCheck' ])
15461621 if self .adopted_jdk_pgo :
15471622 # choose appropriate profiles
15481623 jdk_version = mx_sdk_vm .get_jdk_version_for_profiles ()
@@ -3636,6 +3711,17 @@ def _strip_arg_with_number_gen(_strip_arg, _args):
36363711 result = _strip_arg_with_number_gen (strip_arg , result )
36373712 return list (result )
36383713
3714+ class PerfInvokeProfileCollectionStrategy (Enum ):
3715+ """
3716+ The strategy for extracting virtual invoke method profiles from perf sampling data.
3717+ ALL: Generate a profile for each callsite.
3718+ MULTIPLE_CALLEES: Only generate profiles for callsites with at least 2 different sampled targets.
3719+ """
3720+ ALL = "invoke"
3721+ MULTIPLE_CALLEES = "invoke-multiple"
3722+
3723+ def __str__ (self ):
3724+ return self .value
36393725
36403726class StagesInfo :
36413727 """
0 commit comments