diff options
| author | 2024-08-01 20:18:10 +0000 | |
|---|---|---|
| committer | 2024-08-01 20:18:10 +0000 | |
| commit | b93f614c5a0509cac8c59cd901b8fdbb47716556 (patch) | |
| tree | 546ecb0f665fb69d3009cdade3953d3fa617ae6c | |
| parent | bbdff7fdc6d10648bf2caf06cdaaef4981c75003 (diff) | |
| parent | 22d80bdd1e6fc4aec20ef770945233937dcf33a5 (diff) | |
Merge "Integrate the Debug store into frameworks" into main
| -rw-r--r-- | core/java/android/app/ActivityThread.java | 88 | ||||
| -rw-r--r-- | core/java/android/content/BroadcastReceiver.java | 12 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/DebugStore.java | 247 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/flags.aconfig | 8 | ||||
| -rw-r--r-- | core/jni/Android.bp | 2 | ||||
| -rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
| -rw-r--r-- | core/jni/com_android_internal_os_DebugStore.cpp | 105 | ||||
| -rw-r--r-- | core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java | 311 |
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(); + } +} |