summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mohamad Mahmoud <mohamadmahmoud@google.com> 2024-08-01 20:18:10 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-08-01 20:18:10 +0000
commitb93f614c5a0509cac8c59cd901b8fdbb47716556 (patch)
tree546ecb0f665fb69d3009cdade3953d3fa617ae6c
parentbbdff7fdc6d10648bf2caf06cdaaef4981c75003 (diff)
parent22d80bdd1e6fc4aec20ef770945233937dcf33a5 (diff)
Merge "Integrate the Debug store into frameworks" into main
-rw-r--r--core/java/android/app/ActivityThread.java88
-rw-r--r--core/java/android/content/BroadcastReceiver.java12
-rw-r--r--core/java/com/android/internal/os/DebugStore.java247
-rw-r--r--core/java/com/android/internal/os/flags.aconfig8
-rw-r--r--core/jni/Android.bp2
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/com_android_internal_os_DebugStore.cpp105
-rw-r--r--core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java311
8 files changed, 766 insertions, 9 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2f80b30aed8b..d4558533291f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -232,6 +232,7 @@ import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
+import com.android.internal.os.DebugStore;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SafeZipPathValidatorCallback;
import com.android.internal.os.SomeArgs;
@@ -358,6 +359,15 @@ public final class ActivityThread extends ClientTransactionHandler
private static final long BINDER_CALLBACK_THROTTLE = 10_100L;
private long mBinderCallbackLast = -1;
+ private static final boolean DEBUG_STORE_ENABLED =
+ com.android.internal.os.Flags.debugStoreEnabled();
+
+ /**
+ * Threshold for identifying long-running looper messages (in milliseconds).
+ * Calculated as 2 seconds multiplied by the hardware timeout multiplier.
+ */
+ private static final long LONG_MESSAGE_THRESHOLD_MS = 2000 * Build.HW_TIMEOUT_MULTIPLIER;
+
/**
* Denotes the sequence number of the process state change for which the main thread needs
* to block until the network rules are updated for it.
@@ -2395,6 +2405,12 @@ public final class ActivityThread extends ClientTransactionHandler
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
+ long debugStoreId = -1;
+ // By default, log all long messages when the debug store is enabled,
+ // unless this is overridden for certain message types, for which we have
+ // more granular debug store logging.
+ boolean shouldLogLongMessage = DEBUG_STORE_ENABLED;
+ final long messageStartUptimeMs = SystemClock.uptimeMillis();
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
@@ -2419,24 +2435,61 @@ public final class ActivityThread extends ClientTransactionHandler
"broadcastReceiveComp");
}
}
- handleReceiver((ReceiverData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ ReceiverData receiverData = (ReceiverData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId =
+ DebugStore.recordBroadcastHandleReceiver(receiverData.intent);
+ }
+
+ try {
+ handleReceiver(receiverData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case CREATE_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
("serviceCreate: " + String.valueOf(msg.obj)));
}
- handleCreateService((CreateServiceData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ CreateServiceData createServiceData = (CreateServiceData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId = DebugStore.recordServiceCreate(createServiceData.info);
+ }
+
+ try {
+ handleCreateService(createServiceData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case BIND_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind: "
+ String.valueOf(msg.obj));
}
- handleBindService((BindServiceData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ BindServiceData bindData = (BindServiceData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId =
+ DebugStore.recordServiceBind(bindData.rebind, bindData.intent);
+ }
+ try {
+ handleBindService(bindData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case UNBIND_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -2452,8 +2505,21 @@ public final class ActivityThread extends ClientTransactionHandler
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
("serviceStart: " + String.valueOf(msg.obj)));
}
- handleServiceArgs((ServiceArgsData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ ServiceArgsData serviceData = (ServiceArgsData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId = DebugStore.recordServiceOnStart(serviceData.startId,
+ serviceData.flags, serviceData.args);
+ }
+
+ try {
+ handleServiceArgs(serviceData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case STOP_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -2649,11 +2715,17 @@ public final class ActivityThread extends ClientTransactionHandler
handleFinishInstrumentationWithoutRestart();
break;
}
+ long messageElapsedTimeMs = SystemClock.uptimeMillis() - messageStartUptimeMs;
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
+ if (shouldLogLongMessage
+ && messageElapsedTimeMs > LONG_MESSAGE_THRESHOLD_MS) {
+ DebugStore.recordLongLooperMessage(msg.what, msg.getTarget().getClass().getName(),
+ messageElapsedTimeMs);
+ }
}
}
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index d7195a76d873..964a8be0f153 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -34,6 +34,8 @@ import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.os.DebugStore;
+
/**
* Base class for code that receives and handles broadcast intents sent by
* {@link android.content.Context#sendBroadcast(Intent)}.
@@ -55,6 +57,9 @@ public abstract class BroadcastReceiver {
private PendingResult mPendingResult;
private boolean mDebugUnregister;
+ private static final boolean DEBUG_STORE_ENABLED =
+ com.android.internal.os.Flags.debugStoreEnabled();
+
/**
* State for a result that is pending for a broadcast receiver. Returned
* by {@link BroadcastReceiver#goAsync() goAsync()}
@@ -255,6 +260,9 @@ public abstract class BroadcastReceiver {
"PendingResult#finish#ClassName:" + mReceiverClassName,
1);
}
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordFinish(mReceiverClassName);
+ }
if (mType == TYPE_COMPONENT) {
final IActivityManager mgr = ActivityManager.getService();
@@ -433,7 +441,9 @@ public abstract class BroadcastReceiver {
public final PendingResult goAsync() {
PendingResult res = mPendingResult;
mPendingResult = null;
-
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordGoAsync(getClass().getName());
+ }
if (res != null && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
res.mReceiverClassName = getClass().getName();
Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
diff --git a/core/java/com/android/internal/os/DebugStore.java b/core/java/com/android/internal/os/DebugStore.java
new file mode 100644
index 000000000000..4c45feed8511
--- /dev/null
+++ b/core/java/com/android/internal/os/DebugStore.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2024 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 com.android.internal.os;
+
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+
+/**
+ * The DebugStore class provides methods for recording various debug events related to service
+ * lifecycle, broadcast receivers and others.
+ * The DebugStore class facilitates debugging ANR issues by recording time-stamped events
+ * related to service lifecycles, broadcast receivers, and other framework operations. It logs
+ * the start and end times of operations within the ANR timer scope called by framework,
+ * enabling pinpointing of methods and events contributing to ANRs.
+ *
+ * Usage currently includes recording service starts, binds, and asynchronous operations initiated
+ * by broadcast receivers, providing a granular view of system behavior that facilitates
+ * identifying performance bottlenecks and optimizing issue resolution.
+ *
+ * @hide
+ */
+public class DebugStore {
+ private static DebugStoreNative sDebugStoreNative = new DebugStoreNativeImpl();
+
+ @UnsupportedAppUsage
+ @VisibleForTesting
+ public static void setDebugStoreNative(DebugStoreNative nativeImpl) {
+ sDebugStoreNative = nativeImpl;
+ }
+ /**
+ * Records the start of a service.
+ *
+ * @param startId The start ID of the service.
+ * @param flags Additional flags for the service start.
+ * @param intent The Intent associated with the service start.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordServiceOnStart(int startId, int flags, @Nullable Intent intent) {
+ return sDebugStoreNative.beginEvent(
+ "SvcStart",
+ List.of(
+ "stId",
+ String.valueOf(startId),
+ "flg",
+ Integer.toHexString(flags),
+ "act",
+ Objects.toString(intent != null ? intent.getAction() : null),
+ "comp",
+ Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg",
+ Objects.toString(intent != null ? intent.getPackage() : null)));
+ }
+
+ /**
+ * Records the creation of a service.
+ *
+ * @param serviceInfo Information about the service being created.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordServiceCreate(@Nullable ServiceInfo serviceInfo) {
+ return sDebugStoreNative.beginEvent(
+ "SvcCreate",
+ List.of(
+ "name",
+ Objects.toString(serviceInfo != null ? serviceInfo.name : null),
+ "pkg",
+ Objects.toString(serviceInfo != null ? serviceInfo.packageName : null)));
+ }
+
+ /**
+ * Records the binding of a service.
+ *
+ * @param isRebind Indicates whether the service is being rebound.
+ * @param intent The Intent associated with the service binding.
+ * @return A unique identifier for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordServiceBind(boolean isRebind, @Nullable Intent intent) {
+ return sDebugStoreNative.beginEvent(
+ "SvcBind",
+ List.of(
+ "rebind",
+ String.valueOf(isRebind),
+ "act",
+ Objects.toString(intent != null ? intent.getAction() : null),
+ "cmp",
+ Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg",
+ Objects.toString(intent != null ? intent.getPackage() : null)));
+ }
+
+ /**
+ * Records an asynchronous operation initiated by a broadcast receiver through calling GoAsync.
+ *
+ * @param receiverClassName The class name of the broadcast receiver.
+ */
+ @UnsupportedAppUsage
+ public static void recordGoAsync(String receiverClassName) {
+ sDebugStoreNative.recordEvent(
+ "GoAsync",
+ List.of(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "rcv",
+ Objects.toString(receiverClassName)));
+ }
+
+ /**
+ * Records the completion of a broadcast operation through calling Finish.
+ *
+ * @param receiverClassName The class of the broadcast receiver that completed the operation.
+ */
+ @UnsupportedAppUsage
+ public static void recordFinish(String receiverClassName) {
+ sDebugStoreNative.recordEvent(
+ "Finish",
+ List.of(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "rcv",
+ Objects.toString(receiverClassName)));
+ }
+ /**
+ * Records the completion of a long-running looper message.
+ *
+ * @param messageCode The code representing the type of the message.
+ * @param targetClass The FQN of the class that handled the message.
+ * @param elapsedTimeMs The time that was taken to process the message, in milliseconds.
+ */
+ @UnsupportedAppUsage
+ public static void recordLongLooperMessage(int messageCode, String targetClass,
+ long elapsedTimeMs) {
+ sDebugStoreNative.recordEvent(
+ "LooperMsg",
+ List.of(
+ "code",
+ String.valueOf(messageCode),
+ "trgt",
+ Objects.toString(targetClass),
+ "elapsed",
+ String.valueOf(elapsedTimeMs)));
+ }
+
+
+ /**
+ * Records the reception of a broadcast.
+ *
+ * @param intent The Intent associated with the broadcast.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordBroadcastHandleReceiver(@Nullable Intent intent) {
+ return sDebugStoreNative.beginEvent(
+ "HandleReceiver",
+ List.of(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", Objects.toString(intent != null ? intent.getAction() : null),
+ "cmp", Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg", Objects.toString(intent != null ? intent.getPackage() : null)));
+ }
+
+ /**
+ * Ends a previously recorded event.
+ *
+ * @param id The unique ID of the event to be ended.
+ */
+ @UnsupportedAppUsage
+ public static void recordEventEnd(long id) {
+ sDebugStoreNative.endEvent(id, Collections.emptyList());
+ }
+
+ /**
+ * An interface for a class that acts as a wrapper for the static native methods
+ * of the Debug Store.
+ *
+ * It allows us to mock static native methods in our tests and should be removed
+ * once mocking static methods becomes easier.
+ */
+ @VisibleForTesting
+ public interface DebugStoreNative {
+ /**
+ * Begins an event with the given name and attributes.
+ */
+ long beginEvent(String eventName, List<String> attributes);
+ /**
+ * Ends an event with the given ID and attributes.
+ */
+ void endEvent(long id, List<String> attributes);
+ /**
+ * Records an event with the given name and attributes.
+ */
+ void recordEvent(String eventName, List<String> attributes);
+ }
+
+ private static class DebugStoreNativeImpl implements DebugStoreNative {
+ @Override
+ public long beginEvent(String eventName, List<String> attributes) {
+ return DebugStore.beginEventNative(eventName, attributes);
+ }
+
+ @Override
+ public void endEvent(long id, List<String> attributes) {
+ DebugStore.endEventNative(id, attributes);
+ }
+
+ @Override
+ public void recordEvent(String eventName, List<String> attributes) {
+ DebugStore.recordEventNative(eventName, attributes);
+ }
+ }
+
+ private static native long beginEventNative(String eventName, List<String> attributes);
+
+ private static native void endEventNative(long id, List<String> attributes);
+
+ private static native void recordEventNative(String eventName, List<String> attributes);
+}
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 2ad665181e70..c7117e977ee2 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -19,4 +19,12 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "debug_store_enabled"
+ namespace: "stability"
+ description: "If the debug store is enabled."
+ bug: "314735374"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index ca984c03b174..2abdd57662eb 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -258,6 +258,7 @@ cc_library_shared_for_libandroid_runtime {
"com_android_internal_content_om_OverlayManagerImpl.cpp",
"com_android_internal_net_NetworkUtilsInternal.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
+ "com_android_internal_os_DebugStore.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
"com_android_internal_os_KernelAllocationStats.cpp",
"com_android_internal_os_KernelCpuBpfTracking.cpp",
@@ -315,6 +316,7 @@ cc_library_shared_for_libandroid_runtime {
"libcrypto",
"libcutils",
"libdebuggerd_client",
+ "libdebugstore_cxx",
"libutils",
"libbinder",
"libbinderdebug",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ed59327ff8e9..03b5143ac1f7 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -202,6 +202,7 @@ extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
+extern int register_com_android_internal_os_DebugStore(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_KernelAllocationStats(JNIEnv* env);
extern int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv* env);
@@ -1599,6 +1600,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
+ REG_JNI(register_com_android_internal_os_DebugStore),
REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
REG_JNI(register_com_android_internal_os_LongMultiStateCounter),
REG_JNI(register_com_android_internal_os_Zygote),
diff --git a/core/jni/com_android_internal_os_DebugStore.cpp b/core/jni/com_android_internal_os_DebugStore.cpp
new file mode 100644
index 000000000000..874d6ea18917
--- /dev/null
+++ b/core/jni/com_android_internal_os_DebugStore.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <debugstore/debugstore_cxx_bridge.rs.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+#include <iterator>
+#include <sstream>
+#include <vector>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+ jmethodID mGet;
+ jmethodID mSize;
+} gListClassInfo;
+
+static std::vector<std::string> list_to_vector(JNIEnv* env, jobject jList) {
+ std::vector<std::string> vec;
+ jint size = env->CallIntMethod(jList, gListClassInfo.mSize);
+ if (size % 2 != 0) {
+ std::ostringstream oss;
+
+ std::copy(vec.begin(), vec.end(), std::ostream_iterator<std::string>(oss, ", "));
+ ALOGW("DebugStore list size is odd: %d, elements: %s", size, oss.str().c_str());
+
+ return vec;
+ }
+
+ vec.reserve(size);
+
+ for (jint i = 0; i < size; i++) {
+ ScopedLocalRef<jstring> jEntry(env,
+ reinterpret_cast<jstring>(
+ env->CallObjectMethod(jList, gListClassInfo.mGet,
+ i)));
+ ScopedUtfChars cEntry(env, jEntry.get());
+ vec.emplace_back(cEntry.c_str());
+ }
+ return vec;
+}
+
+static void com_android_internal_os_DebugStore_endEvent(JNIEnv* env, jclass clazz, jlong eventId,
+ jobject jAttributeList) {
+ auto attributes = list_to_vector(env, jAttributeList);
+ debugstore::debug_store_end(static_cast<uint64_t>(eventId), attributes);
+}
+
+static jlong com_android_internal_os_DebugStore_beginEvent(JNIEnv* env, jclass clazz,
+ jstring jeventName,
+ jobject jAttributeList) {
+ ScopedUtfChars eventName(env, jeventName);
+ auto attributes = list_to_vector(env, jAttributeList);
+ jlong eventId =
+ static_cast<jlong>(debugstore::debug_store_begin(eventName.c_str(), attributes));
+ return eventId;
+}
+
+static void com_android_internal_os_DebugStore_recordEvent(JNIEnv* env, jclass clazz,
+ jstring jeventName,
+ jobject jAttributeList) {
+ ScopedUtfChars eventName(env, jeventName);
+ auto attributes = list_to_vector(env, jAttributeList);
+ debugstore::debug_store_record(eventName.c_str(), attributes);
+}
+
+static const JNINativeMethod gDebugStoreMethods[] = {
+ /* name, signature, funcPtr */
+ {"beginEventNative", "(Ljava/lang/String;Ljava/util/List;)J",
+ (void*)com_android_internal_os_DebugStore_beginEvent},
+ {"endEventNative", "(JLjava/util/List;)V",
+ (void*)com_android_internal_os_DebugStore_endEvent},
+ {"recordEventNative", "(Ljava/lang/String;Ljava/util/List;)V",
+ (void*)com_android_internal_os_DebugStore_recordEvent},
+};
+
+int register_com_android_internal_os_DebugStore(JNIEnv* env) {
+ int res = RegisterMethodsOrDie(env, "com/android/internal/os/DebugStore", gDebugStoreMethods,
+ NELEM(gDebugStoreMethods));
+ jclass listClass = FindClassOrDie(env, "java/util/List");
+ gListClassInfo.mGet = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;");
+ gListClassInfo.mSize = GetMethodIDOrDie(env, listClass, "size", "()I");
+
+ return res;
+}
+
+} // namespace android \ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java b/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
new file mode 100644
index 000000000000..786c2fc63018
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2024 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 com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+/**
+ * Test class for {@link DebugStore}.
+ *
+ * To run it:
+ * atest FrameworksCoreTests:com.android.internal.os.DebugStoreTest
+ */
+@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = DebugStore.class)
+@SmallTest
+public class DebugStoreTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Mock
+ private DebugStore.DebugStoreNative mDebugStoreNativeMock;
+
+ @Captor
+ private ArgumentCaptor<List<String>> mListCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ DebugStore.setDebugStoreNative(mDebugStoreNativeMock);
+ }
+
+ @Test
+ public void testRecordServiceOnStart() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidService"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(1L);
+
+ long eventId = DebugStore.recordServiceOnStart(1, 0, intent);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcStart"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "stId", "1",
+ "flg", "0",
+ "act", "com.android.ACTION",
+ "comp", "ComponentInfo{com.android/androidService}",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(1L);
+ }
+
+ @Test
+ public void testRecordServiceCreate() {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.name = "androidService";
+ serviceInfo.packageName = "com.android";
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(2L);
+
+ long eventId = DebugStore.recordServiceCreate(serviceInfo);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcCreate"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "name", "androidService",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(2L);
+ }
+
+ @Test
+ public void testRecordServiceBind() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidService"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(3L);
+
+ long eventId = DebugStore.recordServiceBind(true, intent);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcBind"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "rebind", "true",
+ "act", "com.android.ACTION",
+ "cmp", "ComponentInfo{com.android/androidService}",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(3L);
+ }
+
+ @Test
+ public void testRecordGoAsync() {
+ DebugStore.recordGoAsync("androidReceiver");
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("GoAsync"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "androidReceiver"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordFinish() {
+ DebugStore.recordFinish("androidReceiver");
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("Finish"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "androidReceiver"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordLongLooperMessage() {
+ DebugStore.recordLongLooperMessage(100, "androidHandler", 500L);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("LooperMsg"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "code", "100",
+ "trgt", "androidHandler",
+ "elapsed", "500"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordBroadcastHandleReceiver() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidReceiver"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(4L);
+
+ long eventId = DebugStore.recordBroadcastHandleReceiver(intent);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("HandleReceiver"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", "com.android.ACTION",
+ "cmp", "ComponentInfo{com.android/androidReceiver}",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(4L);
+ }
+
+ @Test
+ public void testRecordEventEnd() {
+ DebugStore.recordEventEnd(1L);
+
+ verify(mDebugStoreNativeMock).endEvent(eq(1L), anyList());
+ }
+
+ @Test
+ public void testRecordServiceOnStartWithNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(5L);
+
+ long eventId = DebugStore.recordServiceOnStart(1, 0, null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcStart"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "stId", "1",
+ "flg", "0",
+ "act", "null",
+ "comp", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(5L);
+ }
+
+ @Test
+ public void testRecordServiceCreateWithNullServiceInfo() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(6L);
+
+ long eventId = DebugStore.recordServiceCreate(null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcCreate"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "name", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(6L);
+ }
+
+ @Test
+ public void testRecordServiceBindWithNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(7L);
+
+ long eventId = DebugStore.recordServiceBind(false, null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcBind"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "rebind", "false",
+ "act", "null",
+ "cmp", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(7L);
+ }
+
+ @Test
+ public void testRecordBroadcastHandleReceiverWithNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(8L);
+
+ long eventId = DebugStore.recordBroadcastHandleReceiver(null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("HandleReceiver"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", "null",
+ "cmp", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(8L);
+ }
+
+ @Test
+ public void testRecordGoAsyncWithNullReceiverClassName() {
+ DebugStore.recordGoAsync(null);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("GoAsync"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "null"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordFinishWithNullReceiverClassName() {
+ DebugStore.recordFinish(null);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("Finish"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "null"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordLongLooperMessageWithNullTargetClass() {
+ DebugStore.recordLongLooperMessage(200, null, 1000L);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("LooperMsg"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "code", "200",
+ "trgt", "null",
+ "elapsed", "1000"
+ ).inOrder();
+ }
+}