[go: nahoru, domu]

CriticalNative and FastNative in tracing-perfetto

Using CriticalNative and FastNative to speed up performance of critical
tracing-perfetto JNI calls.

Additionally, explicitly registering JNI methods with JVM instead of
relying on JNIEXPORT and method name matching.

Bug: 215352263
Test: TrivialTracingBenchmark, PerfettoSdkOverheadBenchmark
Change-Id: I5f088965ed3750f4ba884c04b9dbbe137432faa7
diff --git a/tracing/tracing-perfetto-binary/build.gradle b/tracing/tracing-perfetto-binary/build.gradle
index 11f4655..041c702 100644
--- a/tracing/tracing-perfetto-binary/build.gradle
+++ b/tracing/tracing-perfetto-binary/build.gradle
@@ -78,6 +78,7 @@
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testRunner)
+    androidTestImplementation(project(":benchmark:benchmark-common")) // for supported ABI checks
 }
 
 androidx {
diff --git a/tracing/tracing-perfetto-binary/src/androidTest/java/androidx/tracing/perfetto/binary/test/TracingTest.kt b/tracing/tracing-perfetto-binary/src/androidTest/java/androidx/tracing/perfetto/binary/test/TracingTest.kt
index 520ff8d..4b4e2b2 100644
--- a/tracing/tracing-perfetto-binary/src/androidTest/java/androidx/tracing/perfetto/binary/test/TracingTest.kt
+++ b/tracing/tracing-perfetto-binary/src/androidTest/java/androidx/tracing/perfetto/binary/test/TracingTest.kt
@@ -18,14 +18,41 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import dalvik.system.BaseDexClassLoader
+import java.io.File
+import java.util.zip.ZipFile
+import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class TracingTest {
+    /**
+     * The test verifies that the library was assembled and can be found by the system.
+     * We cannot load the library since it contains explicit JNI method registration tied to
+     * the [androidx.tracing.perfetto.jni.PerfettoNative] class.
+     *
+     * Methods of the library are further tested in e.g.:
+     * - [androidx.tracing.perfetto.jni.test.PerfettoNativeTest]
+     * - [androidx.compose.integration.macrobenchmark.TrivialTracingBenchmark]
+     */
     @Test
-    fun test_loadLibrary() {
-        System.loadLibrary("tracing_perfetto")
+    fun test_library_was_created() {
+        // check that the system can resolve the library
+        val nativeLibraryName = System.mapLibraryName("tracing_perfetto")
+
+        // check that the class loader can find the library
+        val classLoader = javaClass.classLoader as BaseDexClassLoader
+        val libraryPath = classLoader.findLibrary("tracing_perfetto")
+        assertTrue(libraryPath.endsWith("/$nativeLibraryName"))
+
+        // check that the APK contains the library file
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val baseApk = File(context.applicationInfo.publicSourceDir!!)
+        assertTrue(ZipFile(baseApk).entries().asSequence().any {
+            it.name.endsWith("/$nativeLibraryName")
+        })
     }
 }
diff --git a/tracing/tracing-perfetto-binary/src/main/cpp/jni/androidx_tracing_perfetto_jni_PerfettoNative.cc b/tracing/tracing-perfetto-binary/src/main/cpp/jni/androidx_tracing_perfetto_jni_PerfettoNative.cc
index b0c2953..faffb2f 100644
--- a/tracing/tracing-perfetto-binary/src/main/cpp/jni/androidx_tracing_perfetto_jni_PerfettoNative.cc
+++ b/tracing/tracing-perfetto-binary/src/main/cpp/jni/androidx_tracing_perfetto_jni_PerfettoNative.cc
@@ -20,13 +20,13 @@
 
 extern "C" {
 
-JNIEXPORT void JNICALL
+static void JNICALL
 Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeRegisterWithPerfetto(
         JNIEnv *env, __unused jclass clazz) {
     tracing_perfetto::RegisterWithPerfetto();
 }
 
-JNIEXPORT void JNICALL
+static void JNICALL
 Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeTraceEventBegin(
         JNIEnv *env, __unused jclass clazz, jint key, jstring traceInfo) {
     const char *traceInfoUtf = env->GetStringUTFChars(traceInfo, NULL);
@@ -34,16 +34,68 @@
     env->ReleaseStringUTFChars(traceInfo, traceInfoUtf);
 }
 
-JNIEXPORT void JNICALL
-Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeTraceEventEnd(
-        JNIEnv *env, __unused jclass clazz) {
+static void JNICALL
+Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeTraceEventEnd() {
     tracing_perfetto::TraceEventEnd();
 }
 
-JNIEXPORT jstring JNICALL
+static jstring JNICALL
 Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeVersion(
         JNIEnv *env, __unused jclass clazz) {
     return env->NewStringUTF(tracing_perfetto::Version());
 }
 } // extern "C"
 
+// Explicitly registering native methods using CriticalNative / FastNative as per:
+// https://source.android.com/devices/tech/dalvik/improvements#faster-native-methods.
+// Note: this applies to Android 8 - 11. In Android 12+, this is recommended (to avoid slow lookups
+// on first use), but not necessary.
+
+static JNINativeMethod sMethods[] = {
+        {"nativeRegisterWithPerfetto",
+                "()V",
+                reinterpret_cast<void *>(
+                    Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeRegisterWithPerfetto)
+        },
+        {"nativeTraceEventBegin",
+                "(ILjava/lang/String;)V",
+                reinterpret_cast<void *>(
+                    Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeTraceEventBegin)
+        },
+        {"nativeTraceEventEnd",
+                "()V",
+                reinterpret_cast<void *>(
+                    Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeTraceEventEnd)
+        },
+        {"nativeVersion",
+                "()Ljava/lang/String;",
+                reinterpret_cast<void *>(
+                    Java_androidx_tracing_perfetto_jni_PerfettoNative_nativeVersion)
+        },
+};
+
+jint JNI_OnLoad(JavaVM *vm, void * /* reserved */) {
+    JNIEnv *env = NULL;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) {
+        PERFETTO_LOG("JNI_OnLoad failure when trying to register native methods for tracing.");
+        return JNI_ERR;
+    }
+    jclass clazz = env->FindClass("androidx/tracing/perfetto/jni/PerfettoNative");
+    if (clazz == NULL) {
+        PERFETTO_LOG("Cannot find PerfettoNative class when trying to register native methods for "
+                     "tracing.");
+        return JNI_ERR;
+    }
+
+    int result = env->RegisterNatives(clazz, sMethods, 4);
+    env->DeleteLocalRef(clazz);
+
+    if (result != 0) {
+        PERFETTO_LOG("Failure when trying to call RegisterNatives to register native methods for "
+                     "tracing.");
+        return JNI_ERR;
+    }
+
+    PERFETTO_LOG("Successfully registered native methods for tracing.");
+    return JNI_VERSION_1_6;
+}
\ No newline at end of file
diff --git a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
index 419e348..00e6c5a 100644
--- a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
+++ b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
@@ -16,6 +16,8 @@
 package androidx.tracing.perfetto.jni
 
 import androidx.tracing.perfetto.security.SafeLibLoader
+import dalvik.annotation.optimization.CriticalNative
+import dalvik.annotation.optimization.FastNative
 import java.io.File
 
 internal object PerfettoNative {
@@ -25,10 +27,10 @@
     object Metadata {
         const val version = "1.0.0-alpha07"
         val checksums = mapOf(
-            "arm64-v8a" to "abb5fe78f243999cab6fc8ea05e6d0743df3582e7ea4782f5ef2a677d8a0bde7",
-            "armeabi-v7a" to "d40c8487f7bca0917dcd6c0626d179394ef4b28d6a2fed809bc34e8ea15a100e",
-            "x86" to "38e8e5872e2916b993ca5d37894d7973934730bc427af20de4a52f0656a9ce0f",
-            "x86_64" to "ef545e11ceb45bf0eb611d12867bf9b9c5c9361d7e1865f8723a7505d3107ab8",
+            "arm64-v8a" to "3d9fa02a04459e3480f28ae7f3a8ced30f181f02a0e6bc6caf85704faea84857",
+            "armeabi-v7a" to "9eb7bf76a94fce79e682204c9e702c872d15c85f2ebd48de61aa541e7e6de034",
+            "x86" to "6eb72683bf58eeb0175f094d88bc0e00744b1a593dc7eaac9ee15168b1ab9234",
+            "x86_64" to "eabf725464c24c9709d8e2ea073233e5712949bb65aac5c4972528d813b55c74",
         )
     }
 
@@ -36,8 +38,17 @@
 
     fun loadLib(file: File, loader: SafeLibLoader) = loader.loadLib(file, Metadata.checksums)
 
+    @JvmStatic
     external fun nativeRegisterWithPerfetto()
+
+    @FastNative
+    @JvmStatic
     external fun nativeTraceEventBegin(key: Int, traceInfo: String)
+
+    @CriticalNative
+    @JvmStatic
     external fun nativeTraceEventEnd()
+
+    @JvmStatic
     external fun nativeVersion(): String
 }
diff --git a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/CriticalNative.java b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/CriticalNative.java
new file mode 100644
index 0000000..13fbfd2
--- /dev/null
+++ b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/CriticalNative.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.annotation.optimization;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Applied to native methods to enable an ART runtime built-in optimization:
+ * methods that are annotated this way can speed up JNI transitions for methods that contain no
+ * objects (in parameters or return values, or as an implicit {@code this}).
+ *
+ * <p>
+ * The native implementation must exclude the {@code JNIEnv} and {@code jclass} parameters from its
+ * function signature. As an additional limitation, the method must be explicitly registered with
+ * {@code RegisterNatives} instead of relying on the built-in dynamic JNI linking.
+ * </p>
+ *
+ * <p>
+ * Performance of JNI transitions:
+ * <ul>
+ * <li>Regular JNI cost in nanoseconds: 115
+ * <li>Fast {@code (!)} JNI cost in nanoseconds: 60
+ * <li>{@literal @}{@link FastNative} cost in nanoseconds: 35
+ * <li>{@literal @}{@code CriticalNative} cost in nanoseconds: 25
+ * </ul>
+ * (Measured on angler-userdebug in 07/2016).
+ * </p>
+ *
+ * <p>
+ * A similar annotation, {@literal @}{@link FastNative}, exists with similar performance guarantees.
+ * However, unlike {@code @CriticalNative} it supports non-statics, object return values, and object
+ * parameters. If a method absolutely must have access to a {@code jobject}, then use
+ * {@literal @}{@link FastNative} instead of this.
+ * </p>
+ *
+ * <p>
+ * This has the side-effect of disabling all garbage collections while executing a critical native
+ * method. Use with extreme caution. Any long-running methods must not be marked with
+ * {@code @CriticalNative} (including usually-fast but generally unbounded methods)!
+ * </p>
+ *
+ * <p>
+ * <b>Deadlock Warning:</b> As a rule of thumb, do not acquire any locks during a critical native
+ * call if they aren't also locally released [before returning to managed code].
+ * </p>
+ *
+ * <p>
+ * Say some code does:
+ *
+ * <code>
+ * critical_native_call_to_grab_a_lock();
+ * does_some_java_work();
+ * critical_native_call_to_release_a_lock();
+ * </code>
+ *
+ * <p>
+ * This code can lead to deadlocks. Say thread 1 just finishes
+ * {@code critical_native_call_to_grab_a_lock()} and is in {@code does_some_java_work()}.
+ * GC kicks in and suspends thread 1. Thread 2 now is in
+ * {@code critical_native_call_to_grab_a_lock()} but is blocked on grabbing the
+ * native lock since it's held by thread 1. Now thread suspension can't finish
+ * since thread 2 can't be suspended since it's doing CriticalNative JNI.
+ * </p>
+ *
+ * <p>
+ * Normal natives don't have the issue since once it's executing in native code,
+ * it is considered suspended from the runtime's point of view.
+ * CriticalNative natives however don't do the state transition done by the normal natives.
+ * </p>
+ *
+ * <p>
+ * This annotation has no effect when used with non-native methods.
+ * The runtime must throw a {@code VerifierError} upon class loading if this is used with a native
+ * method that contains object parameters, an object return value, or a non-static.
+ * </p>
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@Retention(RetentionPolicy.CLASS)  // Save memory, don't instantiate as an object at runtime.
+@Target(ElementType.METHOD)
+public @interface CriticalNative {}
+
diff --git a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/FastNative.java b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/FastNative.java
new file mode 100644
index 0000000..3eb3745
--- /dev/null
+++ b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/FastNative.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.annotation.optimization;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An ART runtime built-in optimization for "native" methods to speed up JNI transitions.
+ *
+ * <p>
+ * This has the side-effect of disabling all garbage collections while executing a fast native
+ * method. Use with extreme caution. Any long-running methods must not be marked with
+ * {@code @FastNative} (including usually-fast but generally unbounded methods)!</p>
+ *
+ * <p><b>Deadlock Warning:</b>As a rule of thumb, do not acquire any locks during a fast native
+ * call if they aren't also locally released [before returning to managed code].</p>
+ *
+ * <p>
+ * Say some code does:
+ *
+ * <code>
+ * fast_jni_call_to_grab_a_lock();
+ * does_some_java_work();
+ * fast_jni_call_to_release_a_lock();
+ * </code>
+ *
+ * <p>
+ * This code can lead to deadlocks. Say thread 1 just finishes
+ * {@code fast_jni_call_to_grab_a_lock()} and is in {@code does_some_java_work()}.
+ * GC kicks in and suspends thread 1. Thread 2 now is in {@code fast_jni_call_to_grab_a_lock()}
+ * but is blocked on grabbing the native lock since it's held by thread 1.
+ * Now thread suspension can't finish since thread 2 can't be suspended since it's doing
+ * FastNative JNI.
+ * </p>
+ *
+ * <p>
+ * Normal JNI doesn't have the issue since once it's in native code,
+ * it is considered suspended from java's point of view.
+ * FastNative JNI however doesn't do the state transition done by JNI.
+ * </p>
+ *
+ * <p>
+ * Note that even in FastNative methods you <b>are</b> allowed to
+ * allocate objects and make upcalls into Java code. A call from Java to
+ * a FastNative function and back to Java is equivalent to a call from one Java
+ * method to another. What's forbidden in a FastNative method is blocking
+ * the calling thread in some non-Java code and thereby preventing the thread
+ * from responding to requests from the garbage collector to enter the suspended
+ * state.
+ * </p>
+ *
+ * <p>
+ * Has no effect when used with non-native methods.
+ * </p>
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@Retention(RetentionPolicy.CLASS)  // Save memory, don't instantiate as an object at runtime.
+@Target(ElementType.METHOD)
+public @interface FastNative {}
+
diff --git a/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/README.md b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/README.md
new file mode 100644
index 0000000..e604041
--- /dev/null
+++ b/tracing/tracing-perfetto/src/main/java/dalvik/annotation/optimization/README.md
@@ -0,0 +1,9 @@
+# Rationale
+
+`FastNative` and `CriticalNative` annotations are duplicated from the platform, so that the platform
+detects their presence, and applies them, even though they're not part of platform public API.
+
+# Next steps
+
+Once the annotations become public in the platform, remove duplicate files and use the platform.
+Tracking item: b/35664282