Snap for 11520864 from ba25268dce0bf716ec24242f6ddf9000f5dbd5b3 to 24D1-release
Change-Id: I11374900a9ff2403c3b5e90f7a04f22b12a8c54a
diff --git a/framework/java/android/os/ProfilingManager.java b/framework/java/android/os/ProfilingManager.java
index 29b448d..c8c89b4 100644
--- a/framework/java/android/os/ProfilingManager.java
+++ b/framework/java/android/os/ProfilingManager.java
@@ -52,13 +52,14 @@
private static final String TAG = ProfilingManager.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final Object sLock = new Object();
+ private final Object mLock = new Object();
private final Context mContext;
- @GuardedBy("sLock")
- private final ArrayList<ProfilingRequestCallbackWrapper> mCallbacks = new ArrayList<>();
+ /** @hide */
+ @GuardedBy("mLock")
+ public final ArrayList<ProfilingRequestCallbackWrapper> mCallbacks = new ArrayList<>();
- @GuardedBy("sLock")
+ @GuardedBy("mLock")
private IProfilingService mProfilingService;
/**
@@ -108,7 +109,7 @@
@Nullable CancellationSignal cancellationSignal,
@Nullable Executor executor,
@Nullable Consumer<ProfilingResult> listener) {
- synchronized (sLock) {
+ synchronized (mLock) {
try {
final UUID key = UUID.randomUUID();
@@ -130,6 +131,7 @@
if (DEBUG) Log.d(TAG, "ProfilingService is not available");
return;
}
+
// For key, use most and least signifcant bits so we can create an identical UUID
// after passing over binder.
service.requestProfiling(profilingRequest, mContext.getFilesDir().getPath(), tag,
@@ -137,7 +139,7 @@
if (cancellationSignal != null) {
cancellationSignal.setOnCancelListener(
() -> {
- synchronized (sLock) {
+ synchronized (mLock) {
try {
service.requestCancel(key.getMostSignificantBits(),
key.getLeastSignificantBits());
@@ -168,7 +170,7 @@
public void registerForAllProfilingResults(
@NonNull Executor executor,
@NonNull Consumer<ProfilingResult> listener) {
- synchronized (sLock) {
+ synchronized (mLock) {
mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, null));
}
}
@@ -183,7 +185,7 @@
*/
public void unregisterForAllProfilingResults(
@Nullable Consumer<ProfilingResult> listener) {
- synchronized (sLock) {
+ synchronized (mLock) {
if (mCallbacks.isEmpty()) {
// No callbacks, nothing to remove.
return;
@@ -215,7 +217,7 @@
}
@TargetApi(35)
- @GuardedBy("sLock")
+ @GuardedBy("mLock")
private @Nullable IProfilingService getIProfilingServiceLocked() {
if (mProfilingService != null) {
return mProfilingService;
@@ -238,7 +240,7 @@
public void sendResult(@Nullable String resultFile, long keyMostSigBits,
long keyLeastSigBits, int status, @Nullable String tag,
@Nullable String error) {
- synchronized (sLock) {
+ synchronized (mLock) {
if (mCallbacks.isEmpty()) {
// This shouldn't happen - no callbacks, nowhere to report this result.
if (DEBUG) Log.d(TAG, "No callbacks");
diff --git a/framework/proto/android/os/profiling_request.proto b/framework/proto/android/os/profiling_request.proto
index fcf21ec..f8cb046 100644
--- a/framework/proto/android/os/profiling_request.proto
+++ b/framework/proto/android/os/profiling_request.proto
@@ -14,7 +14,9 @@
message StackSampling {
optional uint32 frequency = 1;
}
- message SystemTrace {}
+ message SystemTrace {
+ optional uint32 duration_ms = 1;
+ }
message Config {
oneof cfg_type {
JavaHeapDump java_heap_dump = 1;
diff --git a/service/Android.bp b/service/Android.bp
index bd875c5..dc5cc61 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -31,6 +31,7 @@
libs: [
"framework-annotations-lib",
"framework-profiling.impl",
+ "framework-configinfrastructure",
],
static_libs: [
"modules-utils-build",
diff --git a/service/java/com/android/os/profiling/Configs.java b/service/java/com/android/os/profiling/Configs.java
index 454928a..5eb2757 100644
--- a/service/java/com/android/os/profiling/Configs.java
+++ b/service/java/com/android/os/profiling/Configs.java
@@ -21,6 +21,9 @@
import java.lang.IllegalArgumentException;
public final class Configs {
+
+ static final String HEAP_PROFILE_ART = "heaps: \"com.android.art\"";
+
static final String CONFIG_HEAP_PROFILE = "buffers {\n"
+ " size_kb: 65536\n"
+ "}\n"
@@ -33,14 +36,13 @@
+ " shmem_size_bytes: 8388608\n"
+ " sampling_interval_bytes: {{sampling_interval}}\n"
+ " process_cmdline: \"{{package_name}}\"\n"
- + " {% if {{art}} %}\n"
- + " heaps: \"com.android.art\"\n"
- + " {% endif %}\n"
+ + " {{art}}\n"
+ " }\n"
+ " }\n"
+ "}\n"
+ "\n"
- + "flush_timeout_ms: 30000";
+ + "flush_timeout_ms: 30000\n"
+ + "duration_ms: {{duration}}";
static final String CONFIG_JAVA_HEAP_DUMP = "buffers {\n"
+ " # This is the maximum size of the trace. The buffer will be mmap'd but, only\n"
+ " # the non empty pages contribute to RSS.\n"
@@ -186,8 +188,12 @@
+ "duration_ms: {{duration}}";
// Time to wait beyond trace timeout to ensure perfetto has time to finish writing output.
- private static final int FILE_PROCESSING_DELAY_MS = 1000;
+ private static final int FILE_PROCESSING_DELAY_MS = 5000;
+ private static final int DEFAULT_HEAP_PROFILE_DURATION_MS = 2 * 60 * 1000;
+ // 1 second duration + 100 seconds max wait for dump to finish.
+ private static final int DEFAULT_JAVA_HEAP_DUMP_DURATION_MS = (1 + 100) * 1000;
+ private static final int DEFAULT_STACK_SAMPLING_DURATION_MS = 60 * 1000;
private static final int DEFAULT_TRACE_DURATION_MS = 5 * 60 * 1000;
private static final long DEFAULT_HEAP_PROFILE_SAMPLING_INTERVAL = 4096;
private static final int DEFAULT_STACK_SAMPLING_FREQUENCY = 100;
@@ -226,15 +232,19 @@
? heapProfile.getSamplingIntervalBytes() : sDefaultHeapProfileSamplingInterval;
result = CONFIG_HEAP_PROFILE
.replace("{{sampling_interval}}", String.valueOf(samplingIntervalBytes))
- .replace("{{art}}", String.valueOf(art));
+ .replace("{{art}}", art ? HEAP_PROFILE_ART : "")
+ .replace("{{duration}}", String.valueOf(DEFAULT_HEAP_PROFILE_DURATION_MS));
} else if (config.hasStackSampling()) {
ProfilingRequest.StackSampling stackSampling = config.getStackSampling();
int frequency = stackSampling.hasFrequency()
? stackSampling.getFrequency() : sDefaultStackSamplingFrequency;
result = CONFIG_STACK_SAMPLING.replace("{{frequency}}", String.valueOf(frequency));
} else if (config.hasSystemTrace()) {
+ ProfilingRequest.SystemTrace systemTrace = config.getSystemTrace();
+ int durationMs = systemTrace.hasDurationMs() ? systemTrace.getDurationMs()
+ : sDefaultTraceDurationMs;
result = CONFIG_SYSTEM_TRACE.replace("{{duration}}",
- String.valueOf(sDefaultTraceDurationMs));
+ String.valueOf(durationMs));
// TODO: remove when redaction is hooked up b/327423523
throw new IllegalArgumentException("Trace is not supported until redaction lands");
}
@@ -253,8 +263,28 @@
* in the event that it's not stopped manually.
*/
public static int getPostProcessingScheduleDelayMs(ProfilingRequest request) {
- // TODO select timeout based on type
- // TODO adjust timeout/logic to ensure perfetto is finished
- return sDefaultTraceDurationMs + FILE_PROCESSING_DELAY_MS;
+ // TODO: b/327660454 adjust timeout/logic to ensure perfetto is finished
+ if (!request.hasConfig()) {
+ // Proto has no config, not requesting anything.
+ throw new IllegalArgumentException("Proto config is missing");
+ }
+
+ ProfilingRequest.Config config = request.getConfig();
+
+ // Config can have at most one collection type, find out which and then determine time.
+ if (config.hasJavaHeapDump()) {
+ return DEFAULT_JAVA_HEAP_DUMP_DURATION_MS + FILE_PROCESSING_DELAY_MS;
+ } else if (config.hasHeapProfile()) {
+ return DEFAULT_HEAP_PROFILE_DURATION_MS + FILE_PROCESSING_DELAY_MS;
+ } else if (config.hasStackSampling()) {
+ return DEFAULT_STACK_SAMPLING_DURATION_MS + FILE_PROCESSING_DELAY_MS;
+ } else if (config.hasSystemTrace()) {
+ ProfilingRequest.SystemTrace systemTrace = config.getSystemTrace();
+ return (systemTrace.hasDurationMs() ? systemTrace.getDurationMs()
+ : sDefaultTraceDurationMs) + FILE_PROCESSING_DELAY_MS;
+ }
+
+ // Proto config has no type, we don't know what the app wants.
+ throw new IllegalArgumentException("Proto config type is missing");
}
-}
\ No newline at end of file
+}
diff --git a/service/java/com/android/os/profiling/ProfilingService.java b/service/java/com/android/os/profiling/ProfilingService.java
index ef6c51b..1e4ab37 100644
--- a/service/java/com/android/os/profiling/ProfilingService.java
+++ b/service/java/com/android/os/profiling/ProfilingService.java
@@ -71,21 +71,24 @@
private final int PERFETTO_DESTROY_TIMEOUT_MS;
private final Context mContext;
+ @VisibleForTesting public RateLimiter mRateLimiter = null;
private final HandlerThread mHandlerThread = new HandlerThread("ProfilingService");
private Handler mHandler;
+
// uid indexed collecion of JNI callbacks for results.
- private @Nullable SparseArray<IProfilingResultCallback> mResultCallbacks = new SparseArray<>();
+ @VisibleForTesting
+ public SparseArray<IProfilingResultCallback> mResultCallbacks = new SparseArray<>();
// Request UUID key indexed storage of active tracing sessions. Currently only 1 active session
// is supported at a time, but this will be used in future to support multiple.
- private ArrayMap<String, TracingSession> mTracingSessions = new ArrayMap<>();
+ @VisibleForTesting
+ public ArrayMap<String, TracingSession> mTracingSessions = new ArrayMap<>();
@VisibleForTesting
public ProfilingService(Context context) {
mContext = context;
- RateLimiter.loadFromDisk();
PERFETTO_DESTROY_TIMEOUT_MS = PERFETTO_DESTROY_DEFAULT_TIMEOUT_MS;
mHandlerThread.start();
}
@@ -134,7 +137,9 @@
}
// Check with rate limiter if this request is allowed.
- final int status = RateLimiter.isProfilingRequestAllowed(Binder.getCallingUid(), request);
+ final int status = getRateLimiter().isProfilingRequestAllowed(Binder.getCallingUid(),
+ request);
+ if (DEBUG) Log.d(TAG, "Rate limiter status: " + status);
if (status == RateLimiter.RATE_LIMIT_RESULT_ALLOWED) {
// Rate limiter approved, try to start the request.
try {
@@ -248,7 +253,7 @@
getHandler().postDelayed(session.getProcessResultRunnable(), postProcessingDelayMs);
}
- public void stopProfiling(String key) throws RuntimeException {
+ private void stopProfiling(String key) throws RuntimeException {
TracingSession session = mTracingSessions.get(key);
if (session == null || session.getActiveTrace() == null) {
if (DEBUG) Log.d(TAG, "No active trace, nothing to stop.");
@@ -424,6 +429,13 @@
return mHandler;
}
+ private RateLimiter getRateLimiter() {
+ if (mRateLimiter == null) {
+ mRateLimiter = new RateLimiter(mContext);
+ }
+ return mRateLimiter;
+ }
+
public static final class Lifecycle extends SystemService {
final ProfilingService mService;
diff --git a/service/java/com/android/os/profiling/RateLimiter.java b/service/java/com/android/os/profiling/RateLimiter.java
index 2fdcf42..456560d 100644
--- a/service/java/com/android/os/profiling/RateLimiter.java
+++ b/service/java/com/android/os/profiling/RateLimiter.java
@@ -17,20 +17,26 @@
package android.os.profiling;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.Context;
import android.os.ProfilingRequest;
import android.os.ProfilingResult;
+import android.provider.DeviceConfig;
import android.util.SparseIntArray;
+import com.android.internal.annotations.GuardedBy;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.Queue;
+import java.util.concurrent.Executors;
-public final class RateLimiter {
+public class RateLimiter {
private static final String DEVICE_CONFIG_NAMESPACE = "profiling";
-
- private static final long PERSIST_TO_DISK_FREQUENCY_MS;
+ private static final String DEVICE_CONFIG_RATE_LIMITER_DISABLE_PROPERTY
+ = "rate_limiter.disabled";
private static final long TIME_1_HOUR_MS = 60 * 60 * 1000;
private static final long TIME_24_HOUR_MS = 24 * 60 * 60 * 1000;
@@ -40,17 +46,23 @@
public static final int RATE_LIMIT_RESULT_BLOCKED_PROCESS = 1;
public static final int RATE_LIMIT_RESULT_BLOCKED_SYSTEM = 2;
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+ private final long mPersistToDiskFrequency;
+
/** To be disabled for testing only. */
- private static boolean sRateLimiterEnabled = true;
+ @GuardedBy("mLock")
+ private boolean mRateLimiterEnabled = true;
/** Collection of run costs and entries from the last hour. */
- private static final EntryGroupWrapper sPastRuns1Hour;
+ private final EntryGroupWrapper mPastRuns1Hour;
/** Collection of run costs and entries from the 24 hours. */
- private static final EntryGroupWrapper sPastRuns24Hour;
+ private final EntryGroupWrapper mPastRuns24Hour;
/** Collection of run costs and entries from the 7 days. */
- private static final EntryGroupWrapper sPastRuns7Day;
+ private final EntryGroupWrapper mPastRuns7Day;
- private static long sLastPersistedTimestampMs;
+ private long mLastPersistedTimestampMs;
@IntDef(value={
RATE_LIMIT_RESULT_ALLOWED,
@@ -60,55 +72,77 @@
@Retention(RetentionPolicy.SOURCE)
@interface RateLimitResult {}
- static {
+ public RateLimiter(Context context) {
// TODO: b/324885858 use DeviceConfig for adjustable values.
- sPastRuns1Hour = new EntryGroupWrapper(10, 10, TIME_1_HOUR_MS);
- sPastRuns24Hour = new EntryGroupWrapper(100, 100, TIME_24_HOUR_MS);
- sPastRuns7Day = new EntryGroupWrapper(1000, 1000, TIME_7_DAY_MS);
- PERSIST_TO_DISK_FREQUENCY_MS = 0;
- sLastPersistedTimestampMs = System.currentTimeMillis();
+ mContext = context;
+ mPastRuns1Hour = new EntryGroupWrapper(10, 10, TIME_1_HOUR_MS);
+ mPastRuns24Hour = new EntryGroupWrapper(100, 100, TIME_24_HOUR_MS);
+ mPastRuns7Day = new EntryGroupWrapper(1000, 1000, TIME_7_DAY_MS);
+ mPersistToDiskFrequency = 0;
+ mLastPersistedTimestampMs = System.currentTimeMillis();
+ loadFromDisk();
+
+ // Get initial value for whether rate limiter should be enforcing or if it should always
+ // allow profiling requests. This is used for (automated and manual) testing only.
+ synchronized (mLock) {
+ mRateLimiterEnabled = !DeviceConfig.getBoolean(DEVICE_CONFIG_NAMESPACE,
+ DEVICE_CONFIG_RATE_LIMITER_DISABLE_PROPERTY, false);
+ }
+ // Now subscribe to updates on rate limiter enforcing config.
+ DeviceConfig.addOnPropertiesChangedListener(DEVICE_CONFIG_NAMESPACE,
+ mContext.getMainExecutor(), new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ synchronized (mLock) {
+ mRateLimiterEnabled = properties.getBoolean(
+ DEVICE_CONFIG_RATE_LIMITER_DISABLE_PROPERTY, false);
+ }
+ }
+ });
}
- public static @RateLimitResult int isProfilingRequestAllowed(int uid,
+ public @RateLimitResult int isProfilingRequestAllowed(int uid,
ProfilingRequest request) {
- if (!sRateLimiterEnabled) {
- // Rate limiter is disabled for testing, approve request and don't store cost.
- return RATE_LIMIT_RESULT_ALLOWED;
+ synchronized (mLock) {
+ if (!mRateLimiterEnabled) {
+ // Rate limiter is disabled for testing, approve request and don't store cost.
+ return RATE_LIMIT_RESULT_ALLOWED;
+ }
+ final int cost = 1; // TODO: compute cost b/293957254
+ final long currentTimeMillis = System.currentTimeMillis();
+ int status = mPastRuns1Hour.isProfilingAllowed(uid, cost, currentTimeMillis);
+ if (status == RATE_LIMIT_RESULT_ALLOWED) {
+ status = mPastRuns24Hour.isProfilingAllowed(uid, cost, currentTimeMillis);
+ }
+ if (status == RATE_LIMIT_RESULT_ALLOWED) {
+ status = mPastRuns7Day.isProfilingAllowed(uid, cost, currentTimeMillis);
+ }
+ if (status == RATE_LIMIT_RESULT_ALLOWED) {
+ mPastRuns1Hour.add(uid, cost, currentTimeMillis);
+ mPastRuns24Hour.add(uid, cost, currentTimeMillis);
+ mPastRuns7Day.add(uid, cost, currentTimeMillis);
+ maybePersistToDisk();
+ return RATE_LIMIT_RESULT_ALLOWED;
+ }
+ return status;
}
- final int cost = 1; // TODO: compute cost b/293957254
- final long currentTimeMillis = System.currentTimeMillis();
- int status = sPastRuns1Hour.isProfilingAllowed(uid, cost, currentTimeMillis);
- if (status == RATE_LIMIT_RESULT_ALLOWED) {
- status = sPastRuns24Hour.isProfilingAllowed(uid, cost, currentTimeMillis);
- }
- if (status == RATE_LIMIT_RESULT_ALLOWED) {
- status = sPastRuns7Day.isProfilingAllowed(uid, cost, currentTimeMillis);
- }
- if (status == RATE_LIMIT_RESULT_ALLOWED) {
- sPastRuns1Hour.add(uid, cost, currentTimeMillis);
- sPastRuns24Hour.add(uid, cost, currentTimeMillis);
- sPastRuns7Day.add(uid, cost, currentTimeMillis);
- maybePersistToDisk();
- return RATE_LIMIT_RESULT_ALLOWED;
- }
- return status;
}
- static void maybePersistToDisk() {
- if (PERSIST_TO_DISK_FREQUENCY_MS == 0
- || System.currentTimeMillis() - sLastPersistedTimestampMs
- >= PERSIST_TO_DISK_FREQUENCY_MS) {
+ void maybePersistToDisk() {
+ if (mPersistToDiskFrequency == 0
+ || System.currentTimeMillis() - mLastPersistedTimestampMs
+ >= mPersistToDiskFrequency) {
persistToDisk();
} else {
// TODO: queue persist job b/293957254
}
}
- static void persistToDisk() {
+ void persistToDisk() {
// TODO: b/293957254
}
- static void loadFromDisk() {
+ void loadFromDisk() {
// TODO: b/293957254
}
diff --git a/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java b/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java
index 75aa014..b19233d 100644
--- a/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java
+++ b/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java
@@ -18,17 +18,48 @@
import static com.google.common.truth.Truth.assertThat;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.runner.AndroidJUnit4;
+import static org.mockito.Mockito.reset;
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.os.IProfilingResultCallback;
+import android.os.ParcelFileDescriptor;
+import android.os.Binder;
+import android.content.pm.PackageManager;
+import android.app.UiAutomation;
+import android.os.profiling.RateLimiter;
+import android.os.profiling.TracingSession;
+import android.os.ProfilingRequest;
+import android.os.ProfilingResult;
import android.os.profiling.ProfilingService;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import java.lang.Process;
+import java.util.UUID;
+
+import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
/**
* Tests in this class are for testing the ProfilingService directly without the need to get a
@@ -37,24 +68,253 @@
@RunWith(AndroidJUnit4.class)
public final class ProfilingServiceTests {
+
+ private static final String APP_FILE_PATH = "/data/user/0/com.profiling.test/files";
+ private static final String APP_PACKAGE_NAME = "com.profiling.test";
+ private static final String REQUEST_TAG = "some unique string";
+
+ // Key most and least significant bits are used to generate a unique key specific to each
+ // request. Key is used to pair request back to caller and callbacks so test to keep consistent.
+ private static final long KEY_MOST_SIG_BITS = 456l;
+ private static final long KEY_LEAST_SIG_BITS = 123l;
+
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
- private ProfilingService mProfilingService =
- new ProfilingService(ApplicationProvider.getApplicationContext());
- @Test
- public void createProfilingServiceTest() {
- assertThat(mProfilingService).isNotNull();
- }
+ @Mock private PackageManager mPackageManager;
+ @Mock private Process mActiveTrace;
+ private Context mContext = ApplicationProvider.getApplicationContext();
+ private ProfilingService mProfilingService;
+ private RateLimiter mRateLimiter;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ mProfilingService = spy(new ProfilingService(mContext));
+ mRateLimiter = spy(new RateLimiter(mContext));
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ mProfilingService.mRateLimiter = mRateLimiter;
+ doReturn(APP_PACKAGE_NAME).when(mPackageManager).getNameForUid(anyInt());
+ }
+
+ /** Test that registering binder callbacks works as expected. */
@Test
- public void profilingNotRunningTests() {
- try {
- boolean isRunning = mProfilingService.areAnyTracesRunning();
- assertThat(isRunning).isFalse();
- } catch (Exception exception) {
- assertThat(exception).isInstanceOf(RuntimeException.class);
- }
+ public void testRegisterResultCallback() {
+ ProfilingResultCallback callback = new ProfilingResultCallback();
+
+ // Register callback.
+ mProfilingService.registerResultsCallback(callback);
+
+ // Confirm callback is registered.
+ assertEquals(callback, mProfilingService.mResultCallbacks.get(Binder.getCallingUid()));
+ }
+
+ /**
+ * Test that requesting profiling while another profiling is in progress fails with correct
+ * error codes.
+ */
+ @Test
+ public void testRequestProfiling_ProfilingRunning_Fails() {
+ // Mock traces running check to simulate collection running.
+ doReturn(true).when(mProfilingService).areAnyTracesRunning();
+
+ // Register callback.
+ ProfilingResultCallback callback = new ProfilingResultCallback();
+ mProfilingService.registerResultsCallback(callback);
+
+ // Kick off request.
+ mProfilingService.requestProfiling(ProfilingTestUtils.getJavaHeapDumpProfilingRequest(),
+ APP_FILE_PATH, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+ // Confirm result matches failure expectation.
+ confirmResultCallback(callback, null, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS,
+ ProfilingResult.ERROR_FAILED_PROFILING_IN_PROGRESS, REQUEST_TAG, false);
+ }
+
+ /**
+ * Test that requesting profiling with an invalid request byte array fails with correct error
+ * codes.
+ */
+ @Test
+ public void testRequestProfiling_InvalidRequest_Fails() {
+ // Bypass traces running check, we're not testing that here.
+ doReturn(false).when(mProfilingService).areAnyTracesRunning();
+
+ // Register callback.
+ ProfilingResultCallback callback = new ProfilingResultCallback();
+ mProfilingService.registerResultsCallback(callback);
+
+ // Kick off request.
+ mProfilingService.requestProfiling(new byte[4], APP_FILE_PATH, REQUEST_TAG,
+ KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+ // Confirm result matches failure expectation.
+ confirmResultCallback(callback, null, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS,
+ ProfilingResult.ERROR_FAILED_INVALID_REQUEST, REQUEST_TAG, true);
+ }
+
+ /** Test that requesting where we cannot access the package name fails. */
+ @Test
+ public void testRequestProfiling_PackageNameNotFound_Fails() {
+ // Mock getNameForUid to simulate failure case.
+ doReturn(null).when(mPackageManager).getNameForUid(anyInt());
+
+ // Register callback.
+ ProfilingResultCallback callback = new ProfilingResultCallback();
+ mProfilingService.registerResultsCallback(callback);
+
+ // Kick off request.
+ mProfilingService.requestProfiling(ProfilingTestUtils.getJavaHeapDumpProfilingRequest(),
+ APP_FILE_PATH, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+ // Confirm result matches failure expectation.
+ confirmResultCallback(callback, null, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS,
+ ProfilingResult.ERROR_UNKNOWN, REQUEST_TAG, true);
+ }
+
+ /** Test that failing rate limiting blocks trace from running. */
+ @Test
+ public void testRequestProfiling_RateLimitBlocked_Fails() {
+ // Bypass traces running check, we're not testing that here.
+ doReturn(false).when(mProfilingService).areAnyTracesRunning();
+
+ // Mock rate limiter result to simulate failure case.
+ doReturn(RateLimiter.RATE_LIMIT_RESULT_BLOCKED_PROCESS).when(mRateLimiter)
+ .isProfilingRequestAllowed(anyInt(), anyObject());
+
+ // Register callback.
+ ProfilingResultCallback callback = new ProfilingResultCallback();
+ mProfilingService.registerResultsCallback(callback);
+
+ // Kick off request.
+ mProfilingService.requestProfiling(ProfilingTestUtils.getJavaHeapDumpProfilingRequest(),
+ APP_FILE_PATH, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+ // Confirm result matches failure expectation.
+ confirmResultCallback(callback, null, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS,
+ ProfilingResult.ERROR_FAILED_RATE_LIMIT_PROCESS, REQUEST_TAG, false);
+ }
+
+ /**
+ * Test profiling request with no issues makes it to perfetto kick off and fails because we're
+ * using the wrong context in these tests.
+ */
+ @Test
+ public void testRequestProfiling_Allowed_PerfettoPermissions_Fails() {
+ // Bypass traces running check, we're not testing that here.
+ doReturn(false).when(mProfilingService).areAnyTracesRunning();
+
+ // Register callback.
+ ProfilingResultCallback callback = new ProfilingResultCallback();
+ mProfilingService.registerResultsCallback(callback);
+
+ // Kick off request.
+ mProfilingService.requestProfiling(ProfilingTestUtils.getJavaHeapDumpProfilingRequest(),
+ APP_FILE_PATH, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+ // Perfetto cannot be run from this context, ensure it was attempted and failed permissions.
+ confirmResultCallback(callback, null, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS,
+ ProfilingResult.ERROR_UNKNOWN, REQUEST_TAG, true);
+ assertEquals("Perfetto error", callback.mError);
+ }
+
+ /** Test that checking if any traces are running works when trace is running. */
+ @Test
+ public void testAreAnyTracesRunning_True() {
+ // Ensure no active tracing sessions tracked.
+ mProfilingService.mTracingSessions.clear();
+ assertFalse(mProfilingService.areAnyTracesRunning());
+
+ // Create a tracing session.
+ TracingSession tracingSession = new TracingSession(null, APP_FILE_PATH, 123,
+ APP_PACKAGE_NAME, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+ // Mock tracing session to be running.
+ doReturn(true).when(mActiveTrace).isAlive();
+
+ // Add trace to session and session to ProfilingService tracked sessions.
+ tracingSession.setActiveTrace(mActiveTrace);
+ mProfilingService.mTracingSessions.put(
+ (new UUID(KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS)).toString(), tracingSession);
+
+ // Confirm check returns that a trace is running.
+ assertTrue(mProfilingService.areAnyTracesRunning());
+ }
+
+ /** Test that checking if any traces are running works when trace is not running. */
+ @Test
+ public void testAreAnyTracesRunning_False() {
+ mProfilingService.mTracingSessions.clear();
+ assertFalse(mProfilingService.areAnyTracesRunning());
+
+ TracingSession tracingSession = new TracingSession(null, APP_FILE_PATH, 123,
+ APP_PACKAGE_NAME, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+ mProfilingService.mTracingSessions.put(
+ (new UUID(KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS)).toString(), tracingSession);
+ assertFalse(mProfilingService.areAnyTracesRunning());
+ }
+
+ /** Test that request cancel trace does nothing if no trace is running. */
+ @Test
+ public void testRequestCancel_NotRunning() {
+ // Ensure no active tracing sessions tracked.
+ mProfilingService.mTracingSessions.clear();
+ assertFalse(mProfilingService.areAnyTracesRunning());
+
+ // Register callback.
+ ProfilingResultCallback callback = new ProfilingResultCallback();
+ mProfilingService.registerResultsCallback(callback);
+
+ // Request cancellation.
+ mProfilingService.requestCancel(KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
+
+ // Confirm callback was not triggerd with a result because there was no trace to stop.
+ assertFalse(callback.mResultSent);
+ }
+
+ /** Confirm that all fields returned by callback match expectation. */
+ private void confirmResultCallback(ProfilingResultCallback callback, String resultFile,
+ long keyMostSigBits, long keyLeastSigBits, int status, String tag,
+ boolean errorExpected) {
+ assertEquals(resultFile, callback.mResultFile);
+ assertEquals(keyMostSigBits, callback.mKeyMostSigBits);
+ assertEquals(keyLeastSigBits, callback.mKeyLeastSigBits);
+ assertEquals(status, callback.mStatus);
+ assertEquals(tag, callback.mTag);
+ if (errorExpected) {
+ assertNotNull(callback.mError);
+ } else {
+ assertNull(callback.mError);
+ }
+ }
+
+ public static class ProfilingResultCallback extends IProfilingResultCallback.Stub {
+ boolean mResultSent = false;
+ boolean mFileRequested = false;
+ public String mResultFile;
+ public long mKeyMostSigBits;
+ public long mKeyLeastSigBits;
+ public int mStatus;
+ public String mTag;
+ public String mError;
+ @Override
+ public void sendResult(String resultFile, long keyMostSigBits,
+ long keyLeastSigBits, int status, String tag, String error) {
+ mResultSent = true;
+ mResultFile = resultFile;
+ mKeyMostSigBits = keyMostSigBits;
+ mKeyLeastSigBits = keyLeastSigBits;
+ mStatus = status;
+ mTag = tag;
+ mError = error;
+ }
+ @Override
+ public ParcelFileDescriptor generateFile(String filePathAbsolute, String fileName) {
+ mFileRequested = true;
+ return null;
+ }
}
}
diff --git a/tests/cts/src/android/profiling/cts/ProfilingTestUtils.java b/tests/cts/src/android/profiling/cts/ProfilingTestUtils.java
index e6297f0..e17e2d4 100644
--- a/tests/cts/src/android/profiling/cts/ProfilingTestUtils.java
+++ b/tests/cts/src/android/profiling/cts/ProfilingTestUtils.java
@@ -21,7 +21,6 @@
import android.os.OutcomeReceiver;
import android.os.ParcelUuid;
import android.os.ProfilingRequest;
-import android.os.ProfilingRequest.SystemTrace;
import android.os.ProfilingResult;
import android.os.RemoteException;
import android.util.Log;
@@ -38,11 +37,59 @@
}
public static byte[] getSystemTraceProfilingRequest() {
- SystemTrace systemTrace = ProfilingRequest.SystemTrace.newBuilder().build();
- ProfilingRequest.Config profilingRequestConfig =
- ProfilingRequest.Config.newBuilder().setSystemTrace(systemTrace).build();
- ProfilingRequest profilingRequest =
- ProfilingRequest.newBuilder().setConfig(profilingRequestConfig).build();
- return profilingRequest.toByteArray();
+ // Create system trace proto object.
+ ProfilingRequest.SystemTrace systemTrace
+ = ProfilingRequest.SystemTrace.newBuilder().build();
+
+ // Create config proto object with only system trace type set.
+ ProfilingRequest.Config config
+ = ProfilingRequest.Config.newBuilder().setSystemTrace(systemTrace).build();
+
+ // Create profiling request object with config and convert to byte array.
+ return getProfilingRequestFromConfig(config);
+ }
+
+ public static byte[] getJavaHeapDumpProfilingRequest() {
+ // Create jave heap dump proto object.
+ ProfilingRequest.JavaHeapDump javaHeapDump
+ = ProfilingRequest.JavaHeapDump.newBuilder().build();
+
+ // Create config proto object with only java heap dump type set.
+ ProfilingRequest.Config config
+ = ProfilingRequest.Config.newBuilder().setJavaHeapDump(javaHeapDump).build();
+
+ // Create profiling request object with config and convert to byte array.
+ return getProfilingRequestFromConfig(config);
+ }
+
+ public static byte[] getHeapProfileProfilingRequest() {
+ // Create heap profile proto object.
+ ProfilingRequest.HeapProfile heapProfile
+ = ProfilingRequest.HeapProfile.newBuilder().build();
+
+
+ // Create config proto object with only heap profile type set.
+ ProfilingRequest.Config config
+ = ProfilingRequest.Config.newBuilder().setHeapProfile(heapProfile).build();
+
+ // Create profiling request object with config and convert to byte array.
+ return getProfilingRequestFromConfig(config);
+ }
+
+ public static byte[] getStackSamplingProfilingRequest() {
+ // Create stack sampling proto object.
+ ProfilingRequest.StackSampling stackSampling
+ = ProfilingRequest.StackSampling.newBuilder().build();
+
+ // Create config proto object with only stack sampling type set.
+ ProfilingRequest.Config config
+ = ProfilingRequest.Config.newBuilder().setStackSampling(stackSampling).build();
+
+ // Create profiling request object with config and convert to byte array.
+ return getProfilingRequestFromConfig(config);
+ }
+
+ private static byte[] getProfilingRequestFromConfig(ProfilingRequest.Config config) {
+ return ProfilingRequest.newBuilder().setConfig(config).build().toByteArray();
}
}
\ No newline at end of file