diff --git a/compiler/docs/ReplayCompilation.md b/compiler/docs/ReplayCompilation.md index 6b27ed8b3d3a..79d64b60e22d 100644 --- a/compiler/docs/ReplayCompilation.md +++ b/compiler/docs/ReplayCompilation.md @@ -115,3 +115,53 @@ The `-Djdk.graal.ReplayDivergenceIsFailure=true` argument prevents using default ```shell mx replaycomp -Djdk.graal.ReplayDivergenceIsFailure=true ./replay-files ``` + +## Performance Counters + +When replaying with the `--benchmark` command on the AMD64 Linux platform, the replay launcher can count hardware +performance events via the [PAPI](https://github.com/icl-utk-edu/papi) library. To enable this, it is necessary to set +up PAPI and build the optional PAPI bridge library. Note that recent architectures may not be supported; see the list +here: https://github.com/icl-utk-edu/papi/wiki/Supported-Architectures. + +### PAPI Setup + +PAPI can usually be installed with the package manager (`papi-devel` on Fedora, `libpapi-dev` on Ubuntu). The PAPI +bridge links against the PAPI available on the system. + +To monitor hardware events, it may be necessary to lower the system's restrictions for accessing hardware performance +counters like shown below. + +```shell +sudo sysctl kernel.perf_event_paranoid=-1 +``` + +Additionally, the performance monitoring library (libpfm) may fail to select the appropriate performance monitoring unit +(PMU). The selection can be forced to `amd64` using an environment variable. + +```shell +export LIBPFM_FORCE_PMU=amd64 +``` + +To discover the available counters, use the `papi_avail` and `papi_native_avail` commands, which are part of the PAPI +installation. Verify that a particular event like `PAPI_TOT_INS` (retired instruction count) is counted using the +`papi_command_line` utility. + +```shell +papi_avail +papi_native_avail +papi_command_line PAPI_TOT_INS +``` + +### PAPI Bridge + +The below command builds the PAPI bridge library using the PAPI library available on the system. + +```shell +ENABLE_PAPI_BRIDGE=true mx build --dependencies PAPI_BRIDGE +``` + +The launcher accepts a comma-separated list of event names. The event counts are reported for every benchmark iteration. + +```shell +ENABLE_PAPI_BRIDGE=true mx replaycomp ./replay-files --benchmark --event-names PAPI_TOT_INS +``` diff --git a/compiler/mx.compiler/mx_compiler.py b/compiler/mx.compiler/mx_compiler.py index 5a8be53fe387..08e3f6eeab31 100644 --- a/compiler/mx.compiler/mx_compiler.py +++ b/compiler/mx.compiler/mx_compiler.py @@ -25,7 +25,7 @@ from __future__ import print_function import os -from functools import total_ordering +from functools import total_ordering, lru_cache from os.path import join, exists, basename, dirname, isdir import argparse from argparse import ArgumentParser, RawDescriptionHelpFormatter, REMAINDER @@ -43,6 +43,7 @@ from mx_sdk_benchmark import JVMCI_JDK_TAG, DaCapoBenchmarkSuite, ScalaDaCapoBenchmarkSuite, RenaissanceBenchmarkSuite import mx_graal_benchmark #pylint: disable=unused-import +from mx_cmake import CMakeNinjaProject import mx_gate from mx_gate import Task @@ -1564,6 +1565,7 @@ def mx_register_dynamic_suite_constituents(register_project, register_distributi graal_jdk_dist.description = "GraalJDK CE distribution" graal_jdk_dist.maven = {'groupId': 'org.graalvm', 'tag': 'graaljdk'} register_distribution(graal_jdk_dist) + register_papi_bridge_dynamic_suite_constituents(register_project, register_distribution) def _parse_graaljdk_edition(description, args): @@ -1585,18 +1587,88 @@ def profdiff(args): vm_args = ['-cp', cp, 'org.graalvm.profdiff.Profdiff'] + args return jdk.run_java(args=vm_args) +ENABLE_PAPI_BRIDGE_VAR = 'ENABLE_PAPI_BRIDGE' +"""The environment variable that enables the PAPI bridge library.""" + +def papi_smoke_test(): + """Verifies that PAPI is available and can measure the PAPI_TOT_INS event.""" + command = ['papi_command_line', 'PAPI_TOT_INS'] + capture = mx.OutputCapture() + try: + retval = mx.run(command, nonZeroIsFatal=False, out=capture) + except FileNotFoundError: + mx.abort(f'The PAPI utility `{command[0]}` could not be found in your PATH or is not executable. ' + f'Please ensure PAPI is installed and configured correctly, or unset the {ENABLE_PAPI_BRIDGE_VAR} environment variable.') + output = repr(capture) + if retval != 0 or 'Successfully added' not in output: + mx.abort(f'''The PAPI smoke test failed. Your system may not be supported by PAPI, or it may require some extra +configuration. Note that PAPI may not support recent architectures. + +You can try: + - Widening permissions for performance events, e.g., sudo sysctl kernel.perf_event_paranoid=-1 + - Forcing a specific PMU model, e.g., export LIBPFM_FORCE_PMU=amd64 + +Test command: {" ".join(command)} + +Test output: +{output}''') + +@lru_cache +def papi_bridge_enabled() -> bool: + """Checks if the PAPI bridge library is enabled with an environment variable and runs a smoke test.""" + if mx.get_env(ENABLE_PAPI_BRIDGE_VAR) == 'true': + if mx.get_os() != 'linux' or mx.get_arch() != 'amd64': + mx.abort(f'The PAPI bridge library is not supported on this platform. Please unset the {ENABLE_PAPI_BRIDGE_VAR} environment variable.') + papi_smoke_test() + return True + else: + return False + +PAPI_BRIDGE_DISTRIBUTION = 'PAPI_BRIDGE' +"""The name of the distribution of the PAPI bridge library.""" + +def register_papi_bridge_dynamic_suite_constituents(register_project, register_distribution): + """Registers the PAPI bridge library as a dynamic suite constituent if it is enabled and supported.""" + if papi_bridge_enabled(): + package = 'org.graalvm.papibridge' + register_project(CMakeNinjaProject( + suite=_suite, + name=package, + deps=[], + buildDependencies=['sdk:LLVM_TOOLCHAIN'], + workingSets=None, + subDir='src', + ninja_targets=[''], + results=[''], + vpath=True, + cmakeConfig={'CMAKE_C_COMPILER': '/bin/'}, + )) + register_distribution(mx.LayoutTARDistribution(_suite, + PAPI_BRIDGE_DISTRIBUTION, + [package], + {'./': f'dependency:{package}'}, + None, + True, + None)) + + def replaycomp_vm_args(distributions): """Returns the VM arguments required to run the replay compilation launcher. :param distributions the distributions to add to the classpath :return the list of VM arguments """ + extra_options = [] + if papi_bridge_enabled(): + path = os.path.join(mx.dependency(PAPI_BRIDGE_DISTRIBUTION).get_output(), 'libpapibridge.so') + extra_options.append(f'-Ddebug.jdk.graal.PAPIBridgePath={path}') return [ '-XX:-UseJVMCICompiler', '--enable-native-access=ALL-UNNAMED', '--illegal-native-access=allow', '--add-exports=java.base/jdk.internal.module=ALL-UNNAMED', '-Djdk.graal.CompilationFailureAction=Print', + *extra_options, '-cp', mx.classpath(distributions, jdk=jdk), ] diff --git a/compiler/src/jdk.graal.compiler.libgraal/src/jdk/graal/compiler/libgraal/LibGraalEntryPoints.java b/compiler/src/jdk.graal.compiler.libgraal/src/jdk/graal/compiler/libgraal/LibGraalEntryPoints.java index b167022861a9..787325a4f749 100644 --- a/compiler/src/jdk.graal.compiler.libgraal/src/jdk/graal/compiler/libgraal/LibGraalEntryPoints.java +++ b/compiler/src/jdk.graal.compiler.libgraal/src/jdk/graal/compiler/libgraal/LibGraalEntryPoints.java @@ -33,11 +33,15 @@ import java.util.Map; import org.graalvm.collections.EconomicMap; +import org.graalvm.jniutils.JNI; import org.graalvm.jniutils.JNI.JNIEnv; +import org.graalvm.jniutils.JNICalls; import org.graalvm.jniutils.JNIExceptionWrapper; import org.graalvm.jniutils.JNIMethodScope; +import org.graalvm.jniutils.JNIUtil; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.nativeimage.c.function.CEntryPoint.IsolateThreadContext; import org.graalvm.nativeimage.c.type.CTypeConversion; @@ -52,6 +56,7 @@ import jdk.graal.compiler.hotspot.HotSpotGraalRuntime; import jdk.graal.compiler.hotspot.HotSpotGraalServices; import jdk.graal.compiler.hotspot.ProfileReplaySupport; +import jdk.graal.compiler.hotspot.replaycomp.HardwarePerformanceCounters; import jdk.graal.compiler.hotspot.replaycomp.ReplayCompilationRunner; import jdk.graal.compiler.options.OptionDescriptors; import jdk.graal.compiler.options.OptionKey; @@ -312,7 +317,7 @@ private static int replayCompilation(JNIEnv jniEnv, } else { args = argString.split("\n"); } - return ReplayCompilationRunner.run(args, TTY.out().out()).getStatus(); + return ReplayCompilationRunner.run(args, TTY.out().out(), new LibgraalPAPIBridge(jniEnv)).getStatus(); } catch (Throwable t) { JNIExceptionWrapper.throwInHotSpot(jniEnv, t); return ReplayCompilationRunner.ExitStatus.Failure.getStatus(); @@ -320,4 +325,109 @@ private static int replayCompilation(JNIEnv jniEnv, LibGraalSupportImpl.doReferenceHandling(); } } + + /** + * The implementation that allows libgraal to interact with the PAPI bridge library, + * piggybacking HotSpot's {@link System#load} implementation. + *

+ * The methods can be called from attached threads which provide their {@link JNIEnv} via + * {@link JNIMethodScope}. + * + * @see HardwarePerformanceCounters.PAPIBridge + */ + @SuppressWarnings("try") + private static final class LibgraalPAPIBridge implements HardwarePerformanceCounters.PAPIBridge { + private final JNI.JClass hpcClass; + + private final JNI.JClass stringClass; + + private final JNICalls.JNIMethod linkAndInitializeOnceMethod; + + private final JNICalls.JNIMethod createEventSetMethod; + + private final JNICalls.JNIMethod getNullMethod; + + private final JNICalls.JNIMethod cleanAndDestroyEventSetMethod; + + private final JNICalls.JNIMethod startMethod; + + private final JNICalls.JNIMethod stopMethod; + + LibgraalPAPIBridge(JNIEnv env) { + this.hpcClass = JNIUtil.findClass(env, Word.nullPointer(), JNIUtil.getBinaryName(HardwarePerformanceCounters.class.getName()), true); + this.stringClass = JNIUtil.findClass(env, Word.nullPointer(), JNIUtil.getBinaryName(String.class.getName()), true); + this.linkAndInitializeOnceMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "linkAndInitializeOnce", "()Z"); + this.createEventSetMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "createEventSet", "([Ljava/lang/String;)I"); + this.getNullMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "getNull", "()I"); + this.cleanAndDestroyEventSetMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "cleanAndDestroyEventSet", "(I)Z"); + this.startMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "start", "(I)Z"); + this.stopMethod = JNICalls.JNIMethod.findMethod(env, hpcClass, true, "stop", "(I)[J"); + } + + @Override + public boolean linkAndInitializeOnce() { + JNI.JNIEnv env = JNIMethodScope.env(); + return JNICalls.getDefault().callStaticBoolean(env, hpcClass, linkAndInitializeOnceMethod, StackValue.get(0)); + } + + @Override + public int createEventSet(String[] eventNames) { + JNI.JNIEnv env = JNIMethodScope.env(); + JNI.JObjectArray eventNamesArray = JNIUtil.NewObjectArray(env, eventNames.length, stringClass, Word.nullPointer()); + try { + copyStringArray(env, eventNames, eventNamesArray); + JNI.JValue args = StackValue.get(1, JNI.JValue.class); + args.addressOf(0).setJObject(eventNamesArray); + return JNICalls.getDefault().callStaticInt(env, hpcClass, createEventSetMethod, args); + } finally { + JNIUtil.DeleteLocalRef(env, eventNamesArray); + } + } + + private static void copyStringArray(JNIEnv env, String[] source, JNI.JObjectArray dest) { + for (int i = 0; i < source.length; i++) { + JNI.JString eventName = JNIUtil.createHSString(env, source[i]); + try { + JNIUtil.SetObjectArrayElement(env, dest, i, eventName); + } finally { + JNIUtil.DeleteLocalRef(env, eventName); + } + } + } + + @Override + public int getNull() { + JNI.JNIEnv env = JNIMethodScope.env(); + return JNICalls.getDefault().callStaticInt(env, hpcClass, getNullMethod, StackValue.get(0)); + } + + @Override + public boolean cleanAndDestroyEventSet(int eventset) { + JNI.JNIEnv env = JNIMethodScope.env(); + JNI.JValue args = StackValue.get(1, JNI.JValue.class); + args.addressOf(0).setInt(eventset); + return JNICalls.getDefault().callStaticBoolean(env, hpcClass, cleanAndDestroyEventSetMethod, args); + } + + @Override + public boolean start(int eventset) { + JNI.JNIEnv env = JNIMethodScope.env(); + JNI.JValue args = StackValue.get(1, JNI.JValue.class); + args.addressOf(0).setInt(eventset); + return JNICalls.getDefault().callStaticBoolean(env, hpcClass, startMethod, args); + } + + @Override + public long[] stop(int eventset) { + JNI.JNIEnv env = JNIMethodScope.env(); + JNI.JValue args = StackValue.get(1, JNI.JValue.class); + args.addressOf(0).setInt(eventset); + JNI.JLongArray result = JNICalls.getDefault().callStaticJObject(env, hpcClass, stopMethod, args); + try { + return JNIUtil.createArray(env, result); + } finally { + JNIUtil.DeleteLocalRef(env, result); + } + } + } } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationLauncher.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationLauncher.java index 30407ab1eec8..2f13ac0dd50b 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationLauncher.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationLauncher.java @@ -30,6 +30,7 @@ import com.oracle.truffle.runtime.hotspot.libgraal.LibGraalScope; import jdk.graal.compiler.api.test.ModuleSupport; +import jdk.graal.compiler.hotspot.replaycomp.HardwarePerformanceCounters; import jdk.graal.compiler.hotspot.replaycomp.ReplayCompilationRunner; import jdk.graal.compiler.hotspot.test.LibGraalCompilationDriver; @@ -49,7 +50,8 @@ public class ReplayCompilationLauncher { /** * Calls libgraal C entry point * {@code jdk.graal.compiler.libgraal.LibGraalEntryPoints#replayCompilation}, which in turn - * calls {@link ReplayCompilationRunner#run(String[], PrintStream)}. + * calls + * {@link ReplayCompilationRunner#run(String[], PrintStream, HardwarePerformanceCounters.PAPIBridge)}. */ public static native int runInLibgraal(long isolateThread, long argBuffer); @@ -71,7 +73,7 @@ public static void main(String[] args) { } } else { System.out.println("Running in jargraal"); - ReplayCompilationRunner.run(args, System.out).exitVM(); + ReplayCompilationRunner.run(args, System.out, new HardwarePerformanceCounters.JargraalPAPIBridge()).exitVM(); } } } diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationTest.java index 6571a9c4c55c..dba897a61b43 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/hotspot/replaycomp/test/ReplayCompilationTest.java @@ -36,6 +36,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.graal.compiler.hotspot.replaycomp.HardwarePerformanceCounters; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.EconomicSet; import org.junit.AfterClass; @@ -168,7 +169,7 @@ public void recordAndExecuteReplayRunner() throws Throwable { new String[]{"--compare-graphs=false", temp.path.toString(), "--benchmark", "--iterations=1"} }; for (String[] arguments : argumentLists) { - ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(arguments, TTY.out().out()); + ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(arguments, TTY.out().out(), new HardwarePerformanceCounters.JargraalPAPIBridge()); assertTrue(status == ReplayCompilationRunner.ExitStatus.Success); } }); @@ -182,7 +183,7 @@ public void unparsableReplayFileSucceeds() throws Throwable { * this is not an error. */ assertTrue(Path.of(temp.path.toString(), "empty.json").toFile().createNewFile()); - ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(new String[]{temp.path.toString()}, TTY.out().out()); + ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(new String[]{temp.path.toString()}, TTY.out().out(), new HardwarePerformanceCounters.JargraalPAPIBridge()); assertTrue(status == ReplayCompilationRunner.ExitStatus.Success); }); } @@ -190,7 +191,7 @@ public void unparsableReplayFileSucceeds() throws Throwable { @Test public void emptyLauncherInputFails() throws Throwable { runTest((temp) -> { - ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(new String[]{temp.path.toString()}, TTY.out().out()); + ReplayCompilationRunner.ExitStatus status = ReplayCompilationRunner.run(new String[]{temp.path.toString()}, TTY.out().out(), new HardwarePerformanceCounters.JargraalPAPIBridge()); assertTrue(status == ReplayCompilationRunner.ExitStatus.Failure); }); } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/HardwarePerformanceCounters.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/HardwarePerformanceCounters.java new file mode 100644 index 000000000000..98c8afaf4851 --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/HardwarePerformanceCounters.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.graal.compiler.hotspot.replaycomp; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import jdk.graal.compiler.core.common.LibGraalSupport; +import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.debug.TTY; +import jdk.graal.compiler.options.ExcludeFromJacocoGeneratedReport; +import jdk.graal.compiler.serviceprovider.GraalServices; +import jdk.graal.compiler.util.EconomicHashMap; + +/** + * Provides a Java interface to hardware performance counters using the PAPI library via a JNI + * bridge library. + *

+ * Hardware performance counters are supported on the AMD64 Linux platform only and require the path + * to the library to be passed in the {@link #LIB_PATH_PROPERTY} system property. + *

+ * Thread safety: performance events can be measured from multiple threads with a dedicated + * {@link HardwarePerformanceCounters} instance for each thread. On libgraal, every thread using + * performance counters must be attached, e.g., using + * {@link jdk.graal.compiler.hotspot.HotSpotGraalServiceThread} and + * {@link LibGraalSupport#openCompilationRequestScope()}. + */ +@SuppressWarnings("restricted") +@ExcludeFromJacocoGeneratedReport("requires PAPI and building the bridge library on AMD64 Linux") +public final class HardwarePerformanceCounters implements AutoCloseable { + /** + * An interface for interacting with the PAPI JNI bridge library. This interface enables + * different implementations for libgraal and jargraal. + *

+ * The jargraal implementation directly loads the library and invokes its functions via JNI. + * + *

+     * jargraal --JNI-- libpapibridge.so --dynamic link-- libpapi.so
+     * 
+ * + * Although this scheme works with libgraal, it requires loading the library from libgraal and + * pulling in the {@link System#load} code in the libgraal image. Instead, libgraal piggybacks + * HotSpot's {@link System#load} implementation: + * + *
+     * libgraal --JNI-- HotSpot --JNI-- libpapibridge.so --dynamic link-- libpapi.so
+     * 
+ */ + public interface PAPIBridge { + /** + * Delegates to {@link HardwarePerformanceCounters#linkAndInitializeOnce()}. + */ + boolean linkAndInitializeOnce(); + + /** + * Delegates to {@link HardwarePerformanceCounters#createEventSet(String[])}. + */ + int createEventSet(String[] eventNames); + + /** + * Delegates to {@link HardwarePerformanceCounters#getNull()}. + */ + int getNull(); + + /** + * Delegates to {@link HardwarePerformanceCounters#cleanAndDestroyEventSet(int)}. + */ + boolean cleanAndDestroyEventSet(int eventset); + + /** + * Delegates to {@link HardwarePerformanceCounters#start()}. + */ + boolean start(int eventset); + + /** + * Delegates to {@link HardwarePerformanceCounters#stop()}. + */ + long[] stop(int eventset); + } + + /** + * The jargraal interface for directly interacting with the PAPI JNI bridge library. This + * implementation loads the library and invokes its functions via JNI. + */ + @LibGraalSupport.HostedOnly + public static final class JargraalPAPIBridge implements PAPIBridge { + @Override + public boolean linkAndInitializeOnce() { + return HardwarePerformanceCounters.linkAndInitializeOnce(); + } + + @Override + public int createEventSet(String[] eventNames) { + return HardwarePerformanceCounters.createEventSet(eventNames); + } + + @Override + public int getNull() { + return HardwarePerformanceCounters.getNull(); + } + + @Override + public boolean cleanAndDestroyEventSet(int eventset) { + return HardwarePerformanceCounters.cleanAndDestroyEventSet(eventset); + } + + @Override + public boolean start(int eventset) { + return HardwarePerformanceCounters.start(eventset); + } + + @Override + public long[] stop(int eventset) { + return HardwarePerformanceCounters.stop(eventset); + } + } + + /** + * The property name for the path to the PAPI JNI bridge. + */ + @LibGraalSupport.HostedOnly // + private static final String LIB_PATH_PROPERTY = "debug.jdk.graal.PAPIBridgePath"; + + /** + * Flag to track whether the native library has been initialized. + */ + @LibGraalSupport.HostedOnly // + private static boolean isInitialized = false; + + /** + * Checks that the current platform is supported and initializes the PAPI JNI bridge library. + */ + @LibGraalSupport.HostedOnly + public static synchronized boolean linkAndInitializeOnce() { + if (isInitialized) { + return true; + } + String libraryPath = GraalServices.getSavedProperty(LIB_PATH_PROPERTY); + GraalError.guarantee(libraryPath != null, "no path to the PAPI bridge library was provided"); + System.load(libraryPath); + if (!initialize()) { + TTY.println("Failed to initialize the PAPI bridge library"); + return false; + } + isInitialized = true; + return true; + } + + /** + * The implementation to use for interacting with the PAPI bridge library. + */ + private final PAPIBridge bridge; + + /** + * The list of event names to be measured. + */ + private final List eventNames; + + /** + * The native event set handle. + */ + private int eventSet; + + /** + * The PAPI_NULL constant. + */ + private final int papiNull; + + /** + * Flag to track whether measurements have been started. + */ + private boolean started; + + /** + * Creates a new HardwarePerformanceCounters instance. + * + * @param eventNames the list of event names to be measured + * @param bridge the implementation to use for interacting with the PAPI bridge library + */ + HardwarePerformanceCounters(List eventNames, PAPIBridge bridge) { + this.bridge = bridge; + this.eventNames = List.copyOf(eventNames); + for (String eventName : this.eventNames) { + Objects.requireNonNull(eventName); + } + boolean success = bridge.linkAndInitializeOnce(); + if (!success) { + throw new GraalError("Failed to initialize the PAPI bridge"); + } + this.eventSet = bridge.createEventSet(this.eventNames.toArray(String[]::new)); + this.papiNull = bridge.getNull(); + GraalError.guarantee(this.eventSet != papiNull, "failed to create an event set"); + } + + /** + * Starts measuring the specified events. + */ + public void start() { + boolean success = bridge.start(eventSet); + GraalError.guarantee(success, "failed to start measurements"); + started = true; + } + + /** + * Stops measuring the specified events and returns the counts. + * + * @return a map of event names to their respective counts + */ + public Map stop() { + long[] javaCounts = bridge.stop(eventSet); + GraalError.guarantee(javaCounts != null, "failed to stop measurements"); + started = false; + Map map = new EconomicHashMap<>(eventNames.size()); + for (int i = 0; i < eventNames.size(); i++) { + map.put(eventNames.get(i), javaCounts[i]); + } + return map; + } + + /** + * Releases native resources. + */ + @Override + public void close() { + if (eventSet != papiNull) { + if (started) { + stop(); + } + boolean success = bridge.cleanAndDestroyEventSet(eventSet); + eventSet = papiNull; + GraalError.guarantee(success, "failed to destroy an event set"); + } + } + + /** + * Initializes the PAPI library. + * + * @return true if successful, false otherwise + */ + private static native boolean initialize(); + + /** + * Creates a new PAPI event set and adds named events. + * + * @param eventNames array of event names to add + * @return event set handle or PAPI_NULL + */ + private static native int createEventSet(String[] eventNames); + + /** + * Gets the native PAPI_NULL constant. + * + * @return PAPI_NULL + */ + private static native int getNull(); + + /** + * Cleans up and destroys the given event set. + * + * @param eventset the event set handle + * @return true if successful, false otherwise + */ + private static native boolean cleanAndDestroyEventSet(int eventset); + + /** + * Starts counting events for the given event set. + * + * @param eventset the event set handle + * @return true if successful, false otherwise + */ + private static native boolean start(int eventset); + + /** + * Stops counting events for the given event set and returns the counts. + * + * @param eventset the event set handle + * @return an array of event counts, or null if an error occurs + */ + private static native long[] stop(int eventset); +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationRunner.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationRunner.java index 01c5c5260631..f50ff91fb4e4 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationRunner.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/ReplayCompilationRunner.java @@ -40,6 +40,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -119,10 +120,11 @@ public int getStatus() { * * @param args command-line arguments * @param out output stream for printing messages + * @param bridge the implementation to use for interacting with the PAPI bridge library * @return the exit status of the launcher */ @SuppressWarnings("try") - public static ExitStatus run(String[] args, PrintStream out) { + public static ExitStatus run(String[] args, PrintStream out, HardwarePerformanceCounters.PAPIBridge bridge) { Program program = new Program("mx replaycomp", "Replay compilations from files."); OptionValue verboseArg = program.addNamed("--verbose", new BooleanValue("true|false", false, "Increase the verbosity of the output.")); OptionValue compareGraphsArg = program.addNamed("--compare-graphs", new BooleanValue("true|false", false, "Verify that the replayed graph equals the recorded one.")); @@ -135,7 +137,7 @@ public static ExitStatus run(String[] args, PrintStream out) { if (inputFiles == null) { return ExitStatus.Failure; } - return commandGroup.getSelectedCommand().run(out, inputFiles, verboseArg.getValue(), compareGraphsArg.getValue()); + return commandGroup.getSelectedCommand().run(out, inputFiles, verboseArg.getValue(), compareGraphsArg.getValue(), bridge); } /** @@ -159,9 +161,10 @@ private LauncherCommand(String name, String description) { * @param inputFiles the files that should be replayed * @param verbose increase the verbosity of the output * @param compareGraphs whether the replayed graph should be compared with the recorded one + * @param bridge the implementation to use for interacting with the PAPI bridge library * @return the exit status of the launcher */ - public abstract ExitStatus run(PrintStream out, List inputFiles, boolean verbose, boolean compareGraphs); + public abstract ExitStatus run(PrintStream out, List inputFiles, boolean verbose, boolean compareGraphs, HardwarePerformanceCounters.PAPIBridge bridge); } /** @@ -178,7 +181,7 @@ private ReplayCommand() { @SuppressWarnings("try") @Override - public ExitStatus run(PrintStream out, List inputFiles, boolean verbose, boolean compareGraphs) { + public ExitStatus run(PrintStream out, List inputFiles, boolean verbose, boolean compareGraphs, HardwarePerformanceCounters.PAPIBridge bridge) { OptionValues systemOptions = new OptionValues(HotSpotGraalOptionValues.parseOptions()); OptionValues options = new OptionValues(systemOptions, GraalCompilerOptions.SystemicCompilationFailureRate, 0); CompilerInterfaceDeclarations declarations = CompilerInterfaceDeclarations.build(); @@ -262,17 +265,24 @@ private static final class BenchmarkCommand extends LauncherCommand { private final OptionValue resultsFileArg; + private final OptionValue eventNamesArg; + private BenchmarkCommand() { super("--benchmark", "Replay compilations as a benchmark."); iterationsArg = addNamed("--iterations", new IntegerValue("N", 10, "The number of benchmark iterations.")); resultsFileArg = addNamed("--results-file", new StringValue("RESULTS_FILE", null, "Write benchmark metrics to the file in CSV format.")); + eventNamesArg = addNamed("--event-names", new StringValue("EVENT_NAMES", null, "Comma-separated list of PAPI events to count for each iteration.")); } @SuppressWarnings("try") @Override - public ExitStatus run(PrintStream out, List inputFiles, boolean verbose, boolean compareGraphs) { + public ExitStatus run(PrintStream out, List inputFiles, boolean verbose, boolean compareGraphs, HardwarePerformanceCounters.PAPIBridge bridge) { OptionValues options = new OptionValues(HotSpotGraalOptionValues.parseOptions()); CompilerInterfaceDeclarations declarations = CompilerInterfaceDeclarations.build(); + List eventNames = new ArrayList<>(); + if (eventNamesArg.getValue() != null) { + eventNames = Arrays.asList(eventNamesArg.getValue().split(",")); + } HotSpotJVMCIRuntime runtime = HotSpotJVMCIRuntime.runtime(); CompilerConfigurationFactory factory = CompilerConfigurationFactory.selectFactory(null, options, runtime); LibGraalSupport libgraal = LibGraalSupport.INSTANCE; @@ -305,20 +315,21 @@ public ExitStatus run(PrintStream out, List inputFiles, boolean verbose, b try (PrintStream outStat = (resultsFileArg.isSet()) ? new PrintStream(PathUtilities.openOutputStream(resultsFileArg.getValue())) : null) { for (int i = 0; i < iterationsArg.getValue(); i++) { performCollection(out); - BenchmarkIterationMetrics metrics = new BenchmarkIterationMetrics(i); - metrics.beginIteration(out, outStat); - for (Reproducer reproducer : reproducers) { - try (AutoCloseable ignored = libgraal != null ? libgraal.openCompilationRequestScope() : null) { - ReplayResult replayResult = reproducer.compile(); - replayResult.compareCompilationProducts(compareGraphs); - metrics.addVerifiedResult(replayResult); - } catch (Exception e) { - out.println("Replay failed: " + e); - e.printStackTrace(out); - return ExitStatus.Failure; + try (BenchmarkIterationMetrics metrics = new BenchmarkIterationMetrics(i, eventNames, bridge)) { + metrics.beginIteration(out, outStat); + for (Reproducer reproducer : reproducers) { + try (AutoCloseable ignored = libgraal != null ? libgraal.openCompilationRequestScope() : null) { + ReplayResult replayResult = reproducer.compile(); + replayResult.compareCompilationProducts(compareGraphs); + metrics.addVerifiedResult(replayResult); + } catch (Exception e) { + out.println("Replay failed: " + e); + e.printStackTrace(out); + return ExitStatus.Failure; + } } + metrics.endIteration(out, outStat); } - metrics.endIteration(out, outStat); } } catch (IOException e) { out.println("Failed to write benchmark statistics to " + resultsFileArg.getValue()); @@ -631,7 +642,7 @@ private List failedTasks() { /** * Collects and prints compilation metrics for one iteration of a replay benchmark. */ - private static final class BenchmarkIterationMetrics { + private static final class BenchmarkIterationMetrics implements AutoCloseable { private final int iteration; private long beginWallTime; @@ -646,15 +657,33 @@ private static final class BenchmarkIterationMetrics { private int targetCodeHash; - private BenchmarkIterationMetrics(int iteration) { + private final List eventNames; + + private final HardwarePerformanceCounters performanceCounters; + + private BenchmarkIterationMetrics(int iteration, List eventNames, HardwarePerformanceCounters.PAPIBridge bridge) { this.iteration = iteration; + this.eventNames = eventNames; + if (!eventNames.isEmpty()) { + this.performanceCounters = new HardwarePerformanceCounters(eventNames, bridge); + } else { + this.performanceCounters = null; + } } public void beginIteration(PrintStream out, PrintStream outStat) { if (iteration == 0 && outStat != null) { - outStat.println("iteration,wall_time_ns,thread_time_ns,allocated_memory,compiled_bytecodes,target_code_size,target_code_hash"); + outStat.print("iteration,wall_time_ns,thread_time_ns,allocated_memory,compiled_bytecodes,target_code_size,target_code_hash"); + for (String eventName : eventNames) { + outStat.print(","); + outStat.print(eventName); + } + outStat.println(); } out.printf("====== replaycomp iteration %d started ======%n", iteration); + if (performanceCounters != null) { + performanceCounters.start(); + } beginMemory = getCurrentThreadAllocatedBytes(); beginWallTime = System.nanoTime(); beginThreadTime = getCurrentThreadCpuTime(); @@ -671,18 +700,37 @@ public void endIteration(PrintStream out, PrintStream outStat) { long endThreadTime = getCurrentThreadCpuTime(); long endWallTime = System.nanoTime(); long endMemory = getCurrentThreadAllocatedBytes(); + Map counterValues = Collections.emptyMap(); + if (performanceCounters != null) { + counterValues = performanceCounters.stop(); + } long wallTimeNanos = endWallTime - beginWallTime; long threadTimeNanos = endThreadTime - beginThreadTime; long allocatedMemory = endMemory - beginMemory; if (outStat != null) { - outStat.printf("%d,%d,%d,%d,%d,%d,%08x%n", iteration, wallTimeNanos, threadTimeNanos, allocatedMemory, compiledBytecodes, targetCodeSize, targetCodeHash); + outStat.printf("%d,%d,%d,%d,%d,%d,%08x", iteration, wallTimeNanos, threadTimeNanos, allocatedMemory, compiledBytecodes, targetCodeSize, targetCodeHash); + for (String eventName : eventNames) { + outStat.print(","); + outStat.print(counterValues.get(eventName)); + } + outStat.println(); } out.printf(" Thread time: %12.3f ms%n", threadTimeNanos / ONE_MILLION); out.printf(" Allocated memory: %12.3f MB%n", allocatedMemory / ONE_MILLION); out.printf(" Compiled bytecodes: %12d B%n", compiledBytecodes); out.printf(" Target code size: %12d B%n", targetCodeSize); out.printf(" Target code hash: %08x%n", targetCodeHash); + for (String eventName : eventNames) { + out.printf("%20s: %12d%n", eventName, counterValues.get(eventName)); + } out.printf("====== replaycomp iteration %d completed (%.3f ms) ======%n", iteration, wallTimeNanos / ONE_MILLION); } + + @Override + public void close() { + if (performanceCounters != null) { + performanceCounters.close(); + } + } } } diff --git a/compiler/src/org.graalvm.papibridge/CMakeLists.txt b/compiler/src/org.graalvm.papibridge/CMakeLists.txt new file mode 100644 index 000000000000..d99937e638c8 --- /dev/null +++ b/compiler/src/org.graalvm.papibridge/CMakeLists.txt @@ -0,0 +1,45 @@ +# +# Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +cmake_minimum_required(VERSION 3.10) +project(papibridge) + +set(SRC src/papibridge.c) + +set(INCLUDE_DIRS "$ENV{JAVA_HOME}/include" "$ENV{JAVA_HOME}/include/linux") +if(DEFINED ENV{PAPI_BRIDGE_EXTRA_HEADERS}) + list(APPEND INCLUDE_DIRS "$ENV{PAPI_BRIDGE_EXTRA_HEADERS}") +endif() + +add_library(papibridge SHARED ${SRC}) +target_include_directories(papibridge PRIVATE ${INCLUDE_DIRS}) + +if(DEFINED ENV{PAPI_BRIDGE_PAPI_LIB_DIR}) + target_link_directories(papibridge PRIVATE "$ENV{PAPI_BRIDGE_PAPI_LIB_DIR}") +endif() +target_link_libraries(papibridge PUBLIC papi) + +find_package(Threads REQUIRED) +target_link_libraries(papibridge PUBLIC Threads::Threads) diff --git a/compiler/src/org.graalvm.papibridge/src/papibridge.c b/compiler/src/org.graalvm.papibridge/src/papibridge.c new file mode 100644 index 000000000000..85026b0f2d5c --- /dev/null +++ b/compiler/src/org.graalvm.papibridge/src/papibridge.c @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include + +/** + * Initializes the PAPI library. + * + * @return JNI_TRUE if the initialization is successful, JNI_FALSE otherwise. + */ +JNIEXPORT jboolean JNICALL Java_jdk_graal_compiler_hotspot_replaycomp_HardwarePerformanceCounters_initialize(JNIEnv *env, jclass cls) { + printf("Initializing PAPI\n"); + int retval = PAPI_library_init(PAPI_VER_CURRENT); + if (retval != PAPI_VER_CURRENT) { + fprintf(stderr, "Failed to initialize PAPI %s\n", PAPI_strerror(retval)); + return JNI_FALSE; + } + if ((retval = PAPI_thread_init(pthread_self)) != PAPI_OK) { + fprintf(stderr, "Failed to initialize threads for PAPI %s\n", PAPI_strerror(retval)); + return JNI_FALSE; + } + return JNI_TRUE; +} + +/** + * Creates a new PAPI event set and adds the specified events to it. + * + * @param env The JNI environment. + * @param cls The Java class that this native method belongs to. + * @param eventNames An array of event names to be added to the event set. + * + * @return The handle of the created event set, or PAPI_NULL if an error occurs. + */ +JNIEXPORT jint JNICALL Java_jdk_graal_compiler_hotspot_replaycomp_HardwarePerformanceCounters_createEventSet(JNIEnv *env, jclass cls, + jobjectArray eventNames) { + int eventset = PAPI_NULL; + int retval = PAPI_create_eventset(&eventset); + if (retval != PAPI_OK) { + fprintf(stderr, "Error creating an event set: %s\n", PAPI_strerror(retval)); + return PAPI_NULL; + } + jsize arrayLen = (*env)->GetArrayLength(env, eventNames); + for (jsize i = 0; i < arrayLen; i++) { + jstring eventNameString = (jstring) (*env)->GetObjectArrayElement(env, eventNames, i); + const char *eventName = (*env)->GetStringUTFChars(env, eventNameString, 0); + if ((retval = PAPI_add_named_event(eventset, eventName)) != PAPI_OK) { + fprintf(stderr, "Error adding %s to event set %d: %s\n", eventName, eventset, PAPI_strerror(retval)); + (*env)->ReleaseStringUTFChars(env, eventNameString, eventName); + (*env)->DeleteLocalRef(env, eventNameString); + PAPI_destroy_eventset(&eventset); + return PAPI_NULL; + } + (*env)->ReleaseStringUTFChars(env, eventNameString, eventName); + (*env)->DeleteLocalRef(env, eventNameString); + } + return eventset; +} + +/** + * Returns the PAPI_NULL constant. + * + * @return The PAPI_NULL constant. + */ +JNIEXPORT jint JNICALL Java_jdk_graal_compiler_hotspot_replaycomp_HardwarePerformanceCounters_getNull(JNIEnv *env, jclass cls) { + return PAPI_NULL; +} + +/** + * Cleans up and destroys the specified PAPI event set. + * + * @param env The JNI environment. + * @param cls The Java class that this native method belongs to. + * @param eventset The handle of the event set to be cleaned up and destroyed. + * + * @return JNI_TRUE if the operation is successful, JNI_FALSE otherwise. + */ +JNIEXPORT jboolean JNICALL Java_jdk_graal_compiler_hotspot_replaycomp_HardwarePerformanceCounters_cleanAndDestroyEventSet(JNIEnv *env, jclass cls, + jint eventset) { + int retval = 0; + if ((retval = PAPI_cleanup_eventset(eventset)) != PAPI_OK) { + fprintf(stderr, "Error cleaning up event set %d: %s\n", eventset, PAPI_strerror(retval)); + return JNI_FALSE; + } + if ((retval = PAPI_destroy_eventset(&eventset)) != PAPI_OK) { + fprintf(stderr, "Error destroying event set %d: %s\n", eventset, PAPI_strerror(retval)); + return JNI_FALSE; + } + return JNI_TRUE; +} + +/** + * Starts the measurements for the specified PAPI event set. + * + * @param env The JNI environment. + * @param cls The Java class that this native method belongs to. + * @param eventset The handle of the event set to be started. + * + * @return JNI_TRUE if the operation is successful, JNI_FALSE otherwise. + */ +JNIEXPORT jboolean JNICALL Java_jdk_graal_compiler_hotspot_replaycomp_HardwarePerformanceCounters_start(JNIEnv *env, jclass cls, jint eventset) { + int retval = PAPI_start(eventset); + if (retval != PAPI_OK) { + fprintf(stderr, "Error starting measurements for event set %d: %s\n", eventset, PAPI_strerror(retval)); + return JNI_FALSE; + } + return JNI_TRUE; +} + +/** + * Stops the measurements for the specified PAPI event set and returns the counts. + * + * @param env The JNI environment. + * @param cls The Java class that this native method belongs to. + * @param eventset The handle of the event set to be stopped. + * + * @return A Java long array containing the event counts, or NULL if an error occurs. + */ +JNIEXPORT jlongArray JNICALL Java_jdk_graal_compiler_hotspot_replaycomp_HardwarePerformanceCounters_stop(JNIEnv *env, jclass cls, jint eventset) { + int numberOfEvents = 0; + int retval = PAPI_list_events(eventset, NULL, &numberOfEvents); + if (retval != PAPI_OK || numberOfEvents <= 0) { + fprintf(stderr, "Error: unable to retrieve event count for event set %d: %s\n", eventset, PAPI_strerror(retval)); + return NULL; + } + long long *counts = (long long *) malloc(numberOfEvents * sizeof(long long)); + if (counts == NULL) { + fprintf(stderr, "Error: out of memory for counts\n"); + return NULL; + } + retval = PAPI_stop(eventset, counts); + if (retval != PAPI_OK) { + fprintf(stderr, "Error stopping measurements for event set %d: %s\n", eventset, PAPI_strerror(retval)); + free(counts); + return NULL; + } + jlongArray result = (*env)->NewLongArray(env, numberOfEvents); + if (result == NULL) { + fprintf(stderr, "Error: could not allocate Java long array\n"); + free(counts); + return NULL; + } + (*env)->SetLongArrayRegion(env, result, 0, numberOfEvents, (jlong *) counts); + free(counts); + return result; +}