1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.profiling.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 import static org.mockito.ArgumentMatchers.any;
26 import static org.mockito.ArgumentMatchers.anyBoolean;
27 import static org.mockito.Mockito.spy;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 
31 import android.app.Instrumentation;
32 import android.content.Context;
33 import android.os.Bundle;
34 import android.os.CancellationSignal;
35 import android.os.ProfilingManager;
36 import android.os.ProfilingResult;
37 import android.os.profiling.DeviceConfigHelper;
38 import android.os.profiling.Flags;
39 import android.os.profiling.ProfilingService;
40 import android.platform.test.annotations.RequiresFlagsEnabled;
41 import android.platform.test.flag.junit.CheckFlagsRule;
42 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
43 
44 import androidx.test.core.app.ApplicationProvider;
45 import androidx.test.filters.LargeTest;
46 import androidx.test.platform.app.InstrumentationRegistry;
47 import androidx.test.runner.AndroidJUnit4;
48 
49 import com.android.compatibility.common.util.SystemUtil;
50 
51 import com.google.errorprone.annotations.FormatMethod;
52 
53 import org.junit.After;
54 import org.junit.Before;
55 import org.junit.Rule;
56 import org.junit.Test;
57 import org.junit.rules.TestName;
58 import org.junit.runner.RunWith;
59 import org.testng.TestException;
60 
61 import java.io.File;
62 import java.io.IOException;
63 import java.nio.file.FileSystems;
64 import java.nio.file.Files;
65 import java.nio.file.Path;
66 import java.nio.file.Paths;
67 import java.util.concurrent.atomic.AtomicBoolean;
68 import java.util.function.Consumer;
69 
70 /**
71  *
72  * Tests defined in this class are expected to test the API implementation.  All tests below require
73  * the android.os.profiling.telemetry_apis flag to be enabled, otherwise you will receive an
74  * assumed failure for any tests has the @RequiresFlagsEnabled annotation.
75  *
76  */
77 
78 @RunWith(AndroidJUnit4.class)
79 public final class ProfilingFrameworkTests {
80 
81     // Wait for callback for 5 seconds at a time for up to 60 increments totalling 5 minutes.
82     private static final int CALLBACK_WAIT_TIME_INCREMENT_MS = 5 * 1000;
83     private static final int CALLBACK_WAIT_TIME_INCREMENTS_COUNT = 60;
84 
85     // Smaller number of increments for cancel case - wait for callback for 5 seconds at a time for
86     // up to 4 increments totalling 20 seconds.
87     private static final int CALLBACK_CANCEL_WAIT_TIME_INCREMENTS_COUNT = 4;
88 
89     // Wait for rate limiter config to update for 250 milliseconds at a time for up to 12 increments
90     // totalling 3 seconds.
91     private static final int RATE_LIMITER_WAIT_TIME_INCREMENT_MS = 250;
92     private static final int RATE_LIMITER_WAIT_TIME_INCREMENTS_COUNT = 12;
93 
94     // Wait 2 seconds for profiling to get started before attempting to cancel it.
95     private static final int WAIT_TIME_FOR_PROFILING_START_MS = 2 * 1000;
96 
97     // Keep in sync with {@link ProfilingService} because we can't access it.
98     private static final String OUTPUT_FILE_JAVA_HEAP_DUMP_SUFFIX = ".perfetto-java-heap-dump";
99     private static final String OUTPUT_FILE_HEAP_PROFILE_SUFFIX = ".perfetto-heap-profile";
100     private static final String OUTPUT_FILE_STACK_SAMPLING_SUFFIX = ".perfetto-stack-sample";
101     private static final String OUTPUT_FILE_TRACE_SUFFIX = ".perfetto-trace";
102 
103     public static final Path DUMP_PATH = FileSystems.getDefault()
104             .getPath("/sdcard/ProfilesCollected/");
105 
106     private static final String COMMAND_OVERRIDE_DEVICE_CONFIG_INT = "device_config put %s %s %d";
107     private static final String COMMAND_OVERRIDE_DEVICE_CONFIG_BOOL = "device_config put %s %s %b";
108 
109     private static final int ONE_SECOND_MS = 1 * 1000;
110     private static final int FIVE_SECONDS_MS = 5 * 1000;
111     private static final int TEN_SECONDS_MS = 10 * 1000;
112     private static final int ONE_MINUTE_MS = 60 * 1000;
113     private static final int FIVE_MINUTES_MS = 5 * 60 * 1000;
114     private static final int TEN_MINUTES_MS = 10 * 60 * 1000;
115 
116     private ProfilingManager mProfilingManager = null;
117     private Context mContext = null;
118     private Instrumentation mInstrumentation;
119 
120     @Rule
121     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
122 
123     @Rule
124     public final TestName mTestName = new TestName();
125 
126     @Before
setup()127     public void setup() {
128         mContext = ApplicationProvider.getApplicationContext();
129         mProfilingManager = mContext.getSystemService(ProfilingManager.class);
130         mInstrumentation = InstrumentationRegistry.getInstrumentation();
131 
132         // This permission is required for Headless (HSUM) tests, including Auto.
133         mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
134                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
135 
136         // Disable the rate limiter, we're not testing that in any of these tests.
137         disableRateLimiter();
138     }
139 
140     @SuppressWarnings("GuardedBy") // Suppress warning for mProfilingManager.mProfilingService lock.
141     @After
cleanup()142     public void cleanup() {
143         mProfilingManager.mProfilingService = null;
144     }
145 
146     /** Check and see if we can get a reference to the ProfilingManager service. */
147     @Test
148     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
createServiceTest()149     public void createServiceTest() {
150         assertNotNull(mProfilingManager);
151     }
152 
153     /** Test that request with invalid profiling type fails with correct error output. */
154     @Test
155     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testInvalidProfilingType()156     public void testInvalidProfilingType() {
157         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
158 
159         AppCallback callback = new AppCallback();
160 
161         // This call is passing an invalid profiling request type and should result in an error.
162         mProfilingManager.requestProfiling(
163                 -1,
164                 null,
165                 null,
166                 null,
167                 new ProfilingTestUtils.ImmediateExecutor(),
168                 callback);
169 
170         // Wait until callback#onAccept is triggered so we can confirm the result.
171         waitForCallback(callback);
172 
173         assertEquals(ProfilingResult.ERROR_FAILED_INVALID_REQUEST, callback.mResult.getErrorCode());
174     }
175 
176     /** Test that request with invalid profiling params fails with correct error output. */
177     @Test
178     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testInvalidProfilingParams()179     public void testInvalidProfilingParams() {
180         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
181 
182         AppCallback callback = new AppCallback();
183 
184         Bundle params = new Bundle();
185         params.putBoolean("bypass_rate_limiter", true);
186 
187         // This call is passing a parameters bundle with an invalid parameter and should result in
188         // an error.
189         mProfilingManager.requestProfiling(
190                 ProfilingManager.PROFILING_TYPE_STACK_SAMPLING,
191                 params,
192                 null,
193                 null,
194                 new ProfilingTestUtils.ImmediateExecutor(),
195                 callback);
196 
197         // Wait until callback#onAccept is triggered so we can confirm the result.
198         waitForCallback(callback);
199 
200         assertEquals(ProfilingResult.ERROR_FAILED_INVALID_REQUEST, callback.mResult.getErrorCode());
201     }
202 
203     /** Test that profiling request for java heap dump succeeds and returns a non-empty file. */
204     @Test
205     @LargeTest
206     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testRequestJavaHeapDumpSuccess()207     public void testRequestJavaHeapDumpSuccess() throws Exception {
208         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
209 
210         overrideJavaHeapDumpDeviceConfigValues(false, ONE_SECOND_MS, TEN_SECONDS_MS);
211 
212         AppCallback callback = new AppCallback();
213 
214         // Now kick off the request.
215         mProfilingManager.requestProfiling(
216                 ProfilingManager.PROFILING_TYPE_JAVA_HEAP_DUMP,
217                 null,
218                 null,
219                 null,
220                 new ProfilingTestUtils.ImmediateExecutor(),
221                 callback);
222 
223         // Wait until callback#onAccept is triggered so we can confirm the result.
224         waitForCallback(callback);
225 
226         // Assert that result matches assumptions for success.
227         confirmCollectionSuccess(callback.mResult, OUTPUT_FILE_JAVA_HEAP_DUMP_SUFFIX);
228         dumpTrace(callback.mResult);
229     }
230 
231     /** Test that profiling request for heap profile succeeds and returns a non-empty file. */
232     @Test
233     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testRequestHeapProfileSuccess()234     public void testRequestHeapProfileSuccess() throws Exception {
235         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
236 
237         overrideHeapProfileDeviceConfigValues(false, ONE_SECOND_MS, ONE_SECOND_MS, FIVE_SECONDS_MS);
238 
239         AppCallback callback = new AppCallback();
240 
241         // Add sampling interval param to test because it is currently the only long param.
242         Bundle params = ProfilingTestUtils.getOneSecondDurationParamBundle();
243         params.putLong(ProfilingManager.KEY_SAMPLING_INTERVAL_BYTES, 4096L);
244 
245         // Now kick off the request.
246         mProfilingManager.requestProfiling(
247                 ProfilingManager.PROFILING_TYPE_HEAP_PROFILE,
248                 params,
249                 null,
250                 null,
251                 new ProfilingTestUtils.ImmediateExecutor(),
252                 callback);
253 
254         // Wait until callback#onAccept is triggered so we can confirm the result.
255         waitForCallback(callback);
256 
257         // Assert that result matches assumptions for success.
258         confirmCollectionSuccess(callback.mResult, OUTPUT_FILE_HEAP_PROFILE_SUFFIX);
259         dumpTrace(callback.mResult);
260     }
261 
262     /** Test that profiling request for stack sampling succeeds and returns a non-empty file. */
263     @Test
264     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testRequestStackSamplingSuccess()265     public void testRequestStackSamplingSuccess() throws Exception {
266         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
267 
268         overrideStackSamplingDeviceConfigValues(false, ONE_SECOND_MS, ONE_SECOND_MS,
269                 FIVE_SECONDS_MS);
270 
271         AppCallback callback = new AppCallback();
272 
273         // Now kick off the request.
274         mProfilingManager.requestProfiling(
275                 ProfilingManager.PROFILING_TYPE_STACK_SAMPLING,
276                 ProfilingTestUtils.getOneSecondDurationParamBundle(),
277                 null,
278                 null,
279                 new ProfilingTestUtils.ImmediateExecutor(),
280                 callback);
281 
282         BusyLoopThread busy = new BusyLoopThread();
283 
284         // Wait until callback#onAccept is triggered so we can confirm the result.
285         waitForCallback(callback);
286 
287         busy.stop();
288 
289         // Assert that result matches assumptions for success.
290         confirmCollectionSuccess(callback.mResult, OUTPUT_FILE_STACK_SAMPLING_SUFFIX);
291         dumpTrace(callback.mResult);
292     }
293 
294     /**
295      * Test that profiling request for system trace fails as it's disabled until redaction
296      * is in place.
297      */
298     @Test
299     @RequiresFlagsEnabled({Flags.FLAG_TELEMETRY_APIS, Flags.FLAG_REDACTION_ENABLED})
testRequestSystemTraceSuccess()300     public void testRequestSystemTraceSuccess() throws Exception {
301         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
302 
303         overrideSystemTraceDeviceConfigValues(false, ONE_SECOND_MS, ONE_SECOND_MS, FIVE_SECONDS_MS);
304 
305         AppCallback callback = new AppCallback();
306 
307         // Now kick off the request.
308         mProfilingManager.requestProfiling(
309                 ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE,
310                 ProfilingTestUtils.getOneSecondDurationParamBundle(),
311                 null,
312                 null,
313                 new ProfilingTestUtils.ImmediateExecutor(),
314                 callback);
315 
316         // Wait until callback#onAccept is triggered so we can confirm the result.
317         waitForCallback(callback);
318 
319         // Assert trace has succeeded.
320         confirmCollectionSuccess(callback.mResult, OUTPUT_FILE_TRACE_SUFFIX);
321         dumpTrace(callback.mResult);
322     }
323 
324     /** Test that cancelling java heap dump stops collection and still receives correct result. */
325     @Test
326     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testRequestJavaHeapDumpCancel()327     public void testRequestJavaHeapDumpCancel() throws Exception {
328         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
329 
330         // Set override duration and timeout to 10 minutes so we can ensure it finishes early when
331         // canceled.
332         overrideJavaHeapDumpDeviceConfigValues(false, TEN_MINUTES_MS, TEN_MINUTES_MS);
333 
334         AppCallback callback = new AppCallback();
335         CancellationSignal cancellationSignal = new CancellationSignal();
336 
337         // Now kick off the request.
338         mProfilingManager.requestProfiling(
339                 ProfilingManager.PROFILING_TYPE_JAVA_HEAP_DUMP,
340                 null,    // Use default parameters since we will cancel quickly
341                 null,
342                 cancellationSignal,
343                 new ProfilingTestUtils.ImmediateExecutor(),
344                 callback);
345 
346         // Wait a bit for collection to get started.
347         sleep(WAIT_TIME_FOR_PROFILING_START_MS);
348 
349         // Now request cancellation.
350         cancellationSignal.cancel();
351 
352         // Wait until callback#onAccept is triggered so we can confirm the result.
353         waitForCancelCallback(callback);
354 
355         // Assert that result matches assumptions for success.
356         confirmCollectionSuccess(callback.mResult, OUTPUT_FILE_JAVA_HEAP_DUMP_SUFFIX);
357     }
358 
359     /** Test that cancelling heap profile stops collection and still receives correct result. */
360     @Test
361     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testRequestHeapProfileCancel()362     public void testRequestHeapProfileCancel() throws Exception {
363         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
364 
365         // Set override durations to 10 minutes so we can ensure it finishes early when canceled.
366         overrideHeapProfileDeviceConfigValues(false, TEN_MINUTES_MS, TEN_MINUTES_MS,
367                 TEN_MINUTES_MS);
368 
369         AppCallback callback = new AppCallback();
370         CancellationSignal cancellationSignal = new CancellationSignal();
371 
372         // Now kick off the request.
373         mProfilingManager.requestProfiling(
374                 ProfilingManager.PROFILING_TYPE_HEAP_PROFILE,
375                 null,    // Use default parameters since we will cancel quickly
376                 null,
377                 cancellationSignal,
378                 new ProfilingTestUtils.ImmediateExecutor(),
379                 callback);
380 
381         // Wait a bit for collection to get started.
382         sleep(WAIT_TIME_FOR_PROFILING_START_MS);
383 
384         // Now request cancellation.
385         cancellationSignal.cancel();
386 
387         // Wait until callback#onAccept is triggered so we can confirm the result.
388         waitForCancelCallback(callback);
389 
390         // Assert that result matches assumptions for success.
391         confirmCollectionSuccess(callback.mResult, OUTPUT_FILE_HEAP_PROFILE_SUFFIX);
392     }
393 
394     /** Test that cancelling stack sampling stops collection and still receives correct result. */
395     @Test
396     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testRequestStackSamplingCancel()397     public void testRequestStackSamplingCancel() throws Exception {
398         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
399 
400         // Set override durations to 10 minutes so we can ensure it finishes early when canceled.
401         overrideStackSamplingDeviceConfigValues(false, TEN_MINUTES_MS, TEN_MINUTES_MS,
402                 TEN_MINUTES_MS);
403 
404         AppCallback callback = new AppCallback();
405         CancellationSignal cancellationSignal = new CancellationSignal();
406 
407         // Now kick off the request.
408         mProfilingManager.requestProfiling(
409                 ProfilingManager.PROFILING_TYPE_STACK_SAMPLING,
410                 null,    // Use default parameters since we will cancel quickly
411                 null,
412                 cancellationSignal,
413                 new ProfilingTestUtils.ImmediateExecutor(),
414                 callback);
415 
416         // Wait a bit for collection to get started.
417         sleep(WAIT_TIME_FOR_PROFILING_START_MS);
418 
419         // Now request cancellation.
420         cancellationSignal.cancel();
421 
422         // Wait until callback#onAccept is triggered so we can confirm the result.
423         waitForCancelCallback(callback);
424 
425         // Assert that result matches assumptions for success.
426         confirmCollectionSuccess(callback.mResult, OUTPUT_FILE_STACK_SAMPLING_SUFFIX);
427     }
428 
429     /** Test that cancelling stack sampling stops collection and still receives correct result. */
430     @Test
431     @RequiresFlagsEnabled({Flags.FLAG_TELEMETRY_APIS, Flags.FLAG_REDACTION_ENABLED})
testRequestSystemTraceCancel()432     public void testRequestSystemTraceCancel() throws Exception {
433         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
434 
435         // Set override durations to 10 minutes so we can ensure it finishes early when canceled.
436         overrideSystemTraceDeviceConfigValues(false, TEN_MINUTES_MS, TEN_MINUTES_MS,
437                 TEN_MINUTES_MS);
438 
439         AppCallback callback = new AppCallback();
440         CancellationSignal cancellationSignal = new CancellationSignal();
441 
442         // Now kick off the request.
443         mProfilingManager.requestProfiling(
444                 ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE,
445                 null,    // Use default parameters since we will cancel quickly
446                 null,
447                 cancellationSignal,
448                 new ProfilingTestUtils.ImmediateExecutor(),
449                 callback);
450 
451         // Wait a bit for collection to get started.
452         sleep(WAIT_TIME_FOR_PROFILING_START_MS);
453 
454         // Now request cancellation.
455         cancellationSignal.cancel();
456 
457         // Wait until callback#onAccept is triggered so we can confirm the result.
458         waitForCancelCallback(callback);
459 
460         // Assert that result matches assumptions for success.
461         confirmCollectionSuccess(callback.mResult, OUTPUT_FILE_TRACE_SUFFIX);
462     }
463 
464     /** Test that unregistering a global listener works and that listener does not get called. */
465     @Test
466     @SuppressWarnings("GuardedBy") // Suppress warning for mProfilingManager.mCallbacks lock.
467     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testUnregisterGeneralListener()468     public void testUnregisterGeneralListener() throws Exception {
469         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
470 
471         overrideStackSamplingDeviceConfigValues(false, ONE_SECOND_MS, ONE_SECOND_MS,
472                 FIVE_SECONDS_MS);
473 
474         // Clear all existing callbacks.
475         mProfilingManager.mCallbacks.clear();
476 
477         // Create 2 callbacks.
478         AppCallback callbackSpecific = new AppCallback();
479         AppCallback callbackGeneral = new AppCallback();
480 
481         // Register the general callback.
482         mProfilingManager.registerForAllProfilingResults(
483                 new ProfilingTestUtils.ImmediateExecutor(), callbackGeneral);
484 
485         // Confirm callback is properly registered by checking for size of 1.
486         assertTrue(mProfilingManager.mCallbacks.size() == 1);
487 
488         // Now unregister the general callback.
489         mProfilingManager.unregisterForAllProfilingResults(callbackGeneral);
490 
491         // Now kick off the request.
492         mProfilingManager.requestProfiling(
493                 ProfilingManager.PROFILING_TYPE_STACK_SAMPLING,
494                 ProfilingTestUtils.getOneSecondDurationParamBundle(),
495                 null,
496                 null,
497                 new ProfilingTestUtils.ImmediateExecutor(),
498                 callbackSpecific);
499 
500         // Wait until callback#onAccept is triggered so we can confirm the result.
501         waitForCallback(callbackSpecific);
502 
503         // Assert that the unregistered callback was not triggered.
504         assertNull(callbackGeneral.mResult);
505 
506     }
507 
508     /** Test that a globally registered listener is triggered along with the specific one. */
509     @Test
510     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testTriggerAllListeners()511     public void testTriggerAllListeners() throws Exception {
512         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
513 
514         overrideStackSamplingDeviceConfigValues(false, ONE_SECOND_MS, ONE_SECOND_MS,
515                 FIVE_SECONDS_MS);
516 
517         // Create 3 callbacks.
518         AppCallback callbackSpecific = new AppCallback();
519         AppCallback callbackGeneral1 = new AppCallback();
520         AppCallback callbackGeneral2 = new AppCallback();
521 
522         // Register the first general callback before kicking off request.
523         mProfilingManager.registerForAllProfilingResults(
524                 new ProfilingTestUtils.ImmediateExecutor(), callbackGeneral1);
525 
526         // Now kick off the request.
527         mProfilingManager.requestProfiling(
528                 ProfilingManager.PROFILING_TYPE_STACK_SAMPLING,
529                 ProfilingTestUtils.getOneSecondDurationParamBundle(),
530                 null,
531                 null,
532                 new ProfilingTestUtils.ImmediateExecutor(),
533                 callbackSpecific);
534 
535         // Register the 2nd general callback after kicking off request, but before result is ready.
536         mProfilingManager.registerForAllProfilingResults(
537                 new ProfilingTestUtils.ImmediateExecutor(), callbackGeneral2);
538 
539         // Wait until callback#onAccept is triggered so we can confirm the result.
540         waitForCallback(callbackSpecific);
541 
542         // Assert that result matches assumptions for success in all callbacks.
543         confirmCollectionSuccess(callbackSpecific.mResult, OUTPUT_FILE_STACK_SAMPLING_SUFFIX);
544         confirmCollectionSuccess(callbackGeneral1.mResult, OUTPUT_FILE_STACK_SAMPLING_SUFFIX);
545         confirmCollectionSuccess(callbackGeneral2.mResult, OUTPUT_FILE_STACK_SAMPLING_SUFFIX);
546     }
547 
548     /** Test that listeners registered to the same UID from different contexts are all triggered. */
549     @Test
550     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testTriggerAllListenersDifferentContexts()551     public void testTriggerAllListenersDifferentContexts() throws Exception {
552         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
553 
554         overrideStackSamplingDeviceConfigValues(false, ONE_SECOND_MS, ONE_SECOND_MS,
555                 FIVE_SECONDS_MS);
556 
557         // Obtain another ProfilingManager instance from a different context.
558         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
559         // Confirm the 2 contexts are of a different class. This check is broader than is a strictly
560         // required, but guarantees these contexts cannot be the same.
561         assertFalse(mContext.getClass().equals(context.getClass()));
562         ProfilingManager profilingManager = context.getSystemService(ProfilingManager.class);
563 
564         // Create 3 callbacks.
565         AppCallback callbackSpecific = new AppCallback();
566         AppCallback callbackGeneral1 = new AppCallback();
567         AppCallback callbackGeneral2 = new AppCallback();
568 
569         // Register the general callbacks, one to each context.
570         profilingManager.registerForAllProfilingResults(
571                 new ProfilingTestUtils.ImmediateExecutor(), callbackGeneral1);
572         mProfilingManager.registerForAllProfilingResults(
573                 new ProfilingTestUtils.ImmediateExecutor(), callbackGeneral2);
574 
575         // Now kick off the request.
576         mProfilingManager.requestProfiling(
577                 ProfilingManager.PROFILING_TYPE_STACK_SAMPLING,
578                 ProfilingTestUtils.getOneSecondDurationParamBundle(),
579                 null,
580                 null,
581                 new ProfilingTestUtils.ImmediateExecutor(),
582                 callbackSpecific);
583 
584 
585         // Wait until callback#onAccept is triggered so we can confirm the result.
586         waitForCallback(callbackSpecific);
587 
588         // Assert that result matches assumptions for success in all callbacks.
589         confirmCollectionSuccess(callbackSpecific.mResult, OUTPUT_FILE_STACK_SAMPLING_SUFFIX);
590         confirmCollectionSuccess(callbackGeneral1.mResult, OUTPUT_FILE_STACK_SAMPLING_SUFFIX);
591         confirmCollectionSuccess(callbackGeneral2.mResult, OUTPUT_FILE_STACK_SAMPLING_SUFFIX);
592     }
593 
594     /** Test that profiling request result file name contains the correct tag. */
595     @Test
596     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testRequestTagInFilename()597     public void testRequestTagInFilename() throws Exception {
598         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
599 
600         overrideStackSamplingDeviceConfigValues(false, ONE_SECOND_MS, ONE_SECOND_MS,
601                 FIVE_SECONDS_MS);
602 
603         AppCallback callback = new AppCallback();
604 
605         // Setup tag to use with invalid chars and length, and expected cleaned up version.
606         String fullTag = "TestTag-_-_-12345678901234567890\\\"&:|<>";
607         String tagForFilename = "testtag---1234567890";
608 
609         // Now kick off the request.
610         mProfilingManager.requestProfiling(
611                 ProfilingManager.PROFILING_TYPE_STACK_SAMPLING,
612                 ProfilingTestUtils.getOneSecondDurationParamBundle(),
613                 fullTag,
614                 null,
615                 new ProfilingTestUtils.ImmediateExecutor(),
616                 callback);
617 
618         // Wait until callback#onAccept is triggered so we can confirm the result.
619         waitForCallback(callback);
620 
621         // Assert that used tag matches returned tag.
622         assertTrue(fullTag.equals(callback.mResult.getTag()));
623 
624         // Split the path to obtain the filename.
625         String[] pathArray = callback.mResult.getResultFilePath().split("/");
626         // Then split the filename to obtain the tag section.
627         String[] nameArray = pathArray[pathArray.length - 1].split("_");
628 
629         // Assert that the file name section containing the tag matches the expected filename tag.
630         assertTrue(nameArray[1].equals(tagForFilename));
631     }
632 
633     /** Test that java heap dump killswitch disables collection. */
634     @Test
635     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testJavaHeapDumpKillswitchEnabled()636     public void testJavaHeapDumpKillswitchEnabled() throws Exception {
637         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
638 
639         overrideJavaHeapDumpDeviceConfigValues(true, ONE_SECOND_MS, TEN_SECONDS_MS);
640 
641         AppCallback callback = new AppCallback();
642 
643         // Now kick off the request.
644         mProfilingManager.requestProfiling(
645                 ProfilingManager.PROFILING_TYPE_JAVA_HEAP_DUMP,
646                 null,
647                 null,
648                 null,
649                 new ProfilingTestUtils.ImmediateExecutor(),
650                 callback);
651 
652         // Wait until callback#onAccept is triggered so we can confirm the result.
653         waitForCallback(callback);
654 
655         // Assert that request failed with correct error code.
656         assertEquals(ProfilingResult.ERROR_FAILED_INVALID_REQUEST, callback.mResult.getErrorCode());
657     }
658 
659     /** Test that heap profile killswitch disables collection. */
660     @Test
661     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testHeapProfileKillswitchEnabled()662     public void testHeapProfileKillswitchEnabled() throws Exception {
663         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
664 
665         overrideHeapProfileDeviceConfigValues(true, ONE_SECOND_MS, FIVE_SECONDS_MS, TEN_SECONDS_MS);
666 
667         AppCallback callback = new AppCallback();
668 
669         // Now kick off the request.
670         mProfilingManager.requestProfiling(
671                 ProfilingManager.PROFILING_TYPE_HEAP_PROFILE,
672                 null,
673                 null,
674                 null,
675                 new ProfilingTestUtils.ImmediateExecutor(),
676                 callback);
677 
678         // Wait until callback#onAccept is triggered so we can confirm the result.
679         waitForCallback(callback);
680 
681         // Assert that request failed with correct error code.
682         assertEquals(ProfilingResult.ERROR_FAILED_INVALID_REQUEST, callback.mResult.getErrorCode());
683     }
684 
685     /** Test that stack sampling killswitch disables collection. */
686     @Test
687     @RequiresFlagsEnabled(Flags.FLAG_TELEMETRY_APIS)
testStackSamplingKillswitchEnabled()688     public void testStackSamplingKillswitchEnabled() throws Exception {
689         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
690 
691         overrideStackSamplingDeviceConfigValues(true, ONE_SECOND_MS, FIVE_SECONDS_MS,
692                 TEN_SECONDS_MS);
693 
694         AppCallback callback = new AppCallback();
695 
696         // Now kick off the request.
697         mProfilingManager.requestProfiling(
698                 ProfilingManager.PROFILING_TYPE_STACK_SAMPLING,
699                 null,
700                 null,
701                 null,
702                 new ProfilingTestUtils.ImmediateExecutor(),
703                 callback);
704 
705         // Wait until callback#onAccept is triggered so we can confirm the result.
706         waitForCallback(callback);
707 
708         // Assert that request failed with correct error code.
709         assertEquals(ProfilingResult.ERROR_FAILED_INVALID_REQUEST, callback.mResult.getErrorCode());
710     }
711 
712     /** Test that system trace killswitch disables collection. */
713     @Test
714     @RequiresFlagsEnabled({Flags.FLAG_TELEMETRY_APIS, Flags.FLAG_REDACTION_ENABLED})
testSystemTraceKillswitchEnabled()715     public void testSystemTraceKillswitchEnabled() throws Exception {
716         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
717 
718         overrideSystemTraceDeviceConfigValues(true, ONE_SECOND_MS, FIVE_SECONDS_MS, TEN_SECONDS_MS);
719 
720         AppCallback callback = new AppCallback();
721 
722         // Now kick off the request.
723         mProfilingManager.requestProfiling(
724                 ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE,
725                 null,
726                 null,
727                 null,
728                 new ProfilingTestUtils.ImmediateExecutor(),
729                 callback);
730 
731         // Wait until callback#onAccept is triggered so we can confirm the result.
732         waitForCallback(callback);
733 
734         // Assert that request failed with correct error code.
735         assertEquals(ProfilingResult.ERROR_FAILED_INVALID_REQUEST, callback.mResult.getErrorCode());
736     }
737 
738     /**
739      * Test that adding a new general listener when no listeners have been added to that instance
740      * works correctly, that is: that mProfilingService has been initialized.
741      *
742      * The flow should result in registerResultsCallback being triggered with isGeneralListener true
743      * and generalListenerAdded not being triggered, but we cannot confirm this specifically here.
744      */
745     @SuppressWarnings("GuardedBy") // Suppress warning for mProfilingManager lock.
746     @Test
747     @RequiresFlagsEnabled({Flags.FLAG_TELEMETRY_APIS})
testAddGeneralListenerNoCurrentListeners()748     public void testAddGeneralListenerNoCurrentListeners() throws Exception {
749         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
750 
751         // Setup for no current listener - mProfilingService should be null and mCallbacks empty.
752         mProfilingManager.mProfilingService = null;
753         mProfilingManager.mCallbacks.clear();
754 
755         AppCallback callback = new AppCallback();
756 
757         // Register the general callback.
758         mProfilingManager.registerForAllProfilingResults(new ProfilingTestUtils.ImmediateExecutor(),
759                 callback);
760 
761         // Confirm that mProfilingService has been initialized.
762         assertNotNull(mProfilingManager.mProfilingService);
763     }
764 
765     /**
766      * Test that adding a new profiling instance specific listener when no listeners have been
767      * added to that instance works correctly, that is: that mProfilingService has been initialized.
768      *
769      * The flow should result in registerResultsCallback being triggered with isGeneralListener
770      * false and generalListenerAdded not being triggered, but we cannot confirm this specifically
771      * here.
772      */
773     @SuppressWarnings("GuardedBy") // Suppress warning for mProfilingManager lock.
774     @Test
775     @RequiresFlagsEnabled({Flags.FLAG_TELEMETRY_APIS})
testAddSpecificListenerNoCurrentListeners()776     public void testAddSpecificListenerNoCurrentListeners() throws Exception {
777         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
778 
779         overrideStackSamplingDeviceConfigValues(false, ONE_SECOND_MS, ONE_SECOND_MS,
780                 FIVE_SECONDS_MS);
781 
782         // Setup for no current listener - mProfilingService should be null and mCallbacks empty.
783         mProfilingManager.mProfilingService = null;
784         mProfilingManager.mCallbacks.clear();
785 
786         AppCallback callback = new AppCallback();
787 
788         mProfilingManager.requestProfiling(
789                 ProfilingManager.PROFILING_TYPE_STACK_SAMPLING,
790                 null,
791                 null,
792                 null,
793                 new ProfilingTestUtils.ImmediateExecutor(),
794                 callback);
795 
796         // Confirm that mProfilingService has been initialized.
797         assertNotNull(mProfilingManager.mProfilingService);
798     }
799 
800     /**
801      * Test that adding a new general listener when a listener has already been added to that
802      * instance works correctly, that is: generalListenerAdded is triggered, but
803      * registerResultsCallback is not.
804      */
805     @SuppressWarnings("GuardedBy") // Suppress warning for mProfilingManager lock.
806     @Test
807     @RequiresFlagsEnabled({Flags.FLAG_TELEMETRY_APIS})
testAddGeneralListenerWithCurrentListener()808     public void testAddGeneralListenerWithCurrentListener() throws Exception {
809         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
810 
811         mProfilingManager.mProfilingService = spy(new ProfilingService(mContext));
812 
813         AppCallback callback = new AppCallback();
814 
815         // Register the general callback.
816         mProfilingManager.registerForAllProfilingResults(new ProfilingTestUtils.ImmediateExecutor(),
817                 callback);
818 
819         // Confirm that generalListenerAdded was triggered and registerResultsCallback was not.
820         verify(mProfilingManager.mProfilingService, times(0)).registerResultsCallback(anyBoolean(),
821                 any());
822         verify(mProfilingManager.mProfilingService, times(1)).generalListenerAdded();
823     }
824 
825     /**
826      * Test that adding a new profiling instance specific listener when a listener has already been
827      * added to that instance works correctly, that is: neither registerResultsCallback nor
828      * generalListenerAdded are triggered.
829      */
830     @SuppressWarnings("GuardedBy") // Suppress warning for mProfilingManager lock.
831     @Test
832     @RequiresFlagsEnabled({Flags.FLAG_TELEMETRY_APIS})
testAddSpecificListenerWithCurrentListener()833     public void testAddSpecificListenerWithCurrentListener() throws Exception {
834         if (mProfilingManager == null) throw new TestException("mProfilingManager can not be null");
835 
836         overrideStackSamplingDeviceConfigValues(false, ONE_SECOND_MS, ONE_SECOND_MS,
837                 FIVE_SECONDS_MS);
838 
839         mProfilingManager.mProfilingService = spy(new ProfilingService(mContext));
840 
841         AppCallback callback = new AppCallback();
842 
843         mProfilingManager.requestProfiling(
844                 ProfilingManager.PROFILING_TYPE_STACK_SAMPLING,
845                 null,
846                 null,
847                 null,
848                 new ProfilingTestUtils.ImmediateExecutor(),
849                 callback);
850 
851         // Confirm that neither generalListenerAdded nor registerResultsCallback were triggered.
852         verify(mProfilingManager.mProfilingService, times(0)).registerResultsCallback(anyBoolean(),
853                 any());
854         verify(mProfilingManager.mProfilingService, times(0)).generalListenerAdded();
855     }
856 
857     /** Disable the rate limiter and wait long enough for the update to be picked up. */
disableRateLimiter()858     private void disableRateLimiter() {
859         SystemUtil.runShellCommand(
860                 "device_config put profiling_testing rate_limiter.disabled true");
861         for (int i = 0; i < RATE_LIMITER_WAIT_TIME_INCREMENTS_COUNT; i++) {
862             sleep(RATE_LIMITER_WAIT_TIME_INCREMENT_MS);
863             String output = SystemUtil.runShellCommand(
864                     "device_config get profiling_testing rate_limiter.disabled");
865             if (Boolean.parseBoolean(output.trim())) {
866                 return;
867             }
868 
869         }
870     }
871 
872     /** Wait for callback to be triggered. Waits for up to 5 minutes, checking every 5 seconds. */
waitForCallback(AppCallback callback)873     private void waitForCallback(AppCallback callback) {
874         waitForCallback(callback, CALLBACK_WAIT_TIME_INCREMENT_MS,
875                 CALLBACK_WAIT_TIME_INCREMENTS_COUNT);
876     }
877 
878     /**
879      * Wait for callback to be triggered after cancellation. Waits for up to 20 seconds, checking
880      * every 5 seconds.
881      */
waitForCancelCallback(AppCallback callback)882     private void waitForCancelCallback(AppCallback callback) {
883         waitForCallback(callback, CALLBACK_WAIT_TIME_INCREMENT_MS,
884                 CALLBACK_CANCEL_WAIT_TIME_INCREMENTS_COUNT);
885     }
886 
887     /**
888      * Wait for callback to be triggered. Waits up to incrementMs * count, checking every
889      * incrementMs milliseconds.
890      */
waitForCallback(AppCallback callback, int incrementMs, int count)891     private void waitForCallback(AppCallback callback, int incrementMs, int count) {
892         for (int i = 0; i < count; i++) {
893             sleep(incrementMs);
894             if (callback.mResult != null) {
895                 return;
896             }
897         }
898         fail("Test timed out waiting for callback");
899     }
900 
901     /** Assert that result matches a success case, specifically: contains a path and no errors. */
confirmCollectionSuccess(ProfilingResult result, String suffix)902     private void confirmCollectionSuccess(ProfilingResult result, String suffix) {
903         assertNotNull(result);
904         assertEquals(ProfilingResult.ERROR_NONE, result.getErrorCode());
905         assertNotNull(result.getResultFilePath());
906         assertTrue(result.getResultFilePath().contains(suffix));
907         assertNull(result.getErrorMessage());
908 
909         // Confirm output file exists and is not empty.
910         File file = new File(result.getResultFilePath());
911         assertTrue(file.exists());
912         assertFalse(file.length() == 0);
913     }
914 
915     /** Copies the trace to an /sdcard directory that will be collected by the test runner. */
dumpTrace(ProfilingResult result)916     private void dumpTrace(ProfilingResult result) {
917         assertNotNull(result);
918         assertEquals(ProfilingResult.ERROR_NONE, result.getErrorCode());
919         assertNotNull(result.getResultFilePath());
920         assertNull(result.getErrorMessage());
921 
922         // Copy to dump directory
923         Path path = Paths.get(result.getResultFilePath());
924         try {
925             Files.createDirectories(DUMP_PATH);
926             String filename = mTestName.getMethodName() + "_" + path.getFileName()
927                     + ".perfetto-trace";
928             Files.copy(path, Paths.get(DUMP_PATH.toString(), filename));
929         } catch (IOException e) {
930             throw new AssertionError("Failed to copy to DUMP_PATH", e);
931         }
932     }
933 
overrideJavaHeapDumpDeviceConfigValues(boolean killswitchEnabled, int durationMs, int dataSourceTimeoutMs)934     private void overrideJavaHeapDumpDeviceConfigValues(boolean killswitchEnabled, int durationMs,
935             int dataSourceTimeoutMs) throws Exception {
936         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_BOOL, DeviceConfigHelper.NAMESPACE,
937                 DeviceConfigHelper.KILLSWITCH_JAVA_HEAP_DUMP, killswitchEnabled);
938         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
939                 DeviceConfigHelper.JAVA_HEAP_DUMP_DURATION_MS_DEFAULT, durationMs);
940         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
941                 DeviceConfigHelper.JAVA_HEAP_DUMP_DATA_SOURCE_STOP_TIMEOUT_MS_DEFAULT,
942                 dataSourceTimeoutMs);
943     }
944 
overrideHeapProfileDeviceConfigValues(boolean killswitchEnabled, int durationDefaultMs, int durationMinMs, int durationMaxMs)945     private void overrideHeapProfileDeviceConfigValues(boolean killswitchEnabled,
946             int durationDefaultMs, int durationMinMs, int durationMaxMs) throws Exception {
947         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_BOOL, DeviceConfigHelper.NAMESPACE,
948                 DeviceConfigHelper.KILLSWITCH_HEAP_PROFILE, killswitchEnabled);
949         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
950                 DeviceConfigHelper.HEAP_PROFILE_DURATION_MS_DEFAULT, durationDefaultMs);
951         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
952                 DeviceConfigHelper.HEAP_PROFILE_DURATION_MS_MIN, durationMinMs);
953         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
954                 DeviceConfigHelper.HEAP_PROFILE_DURATION_MS_MAX, durationMaxMs);
955     }
956 
overrideStackSamplingDeviceConfigValues(boolean killswitchEnabled, int durationDefaultMs, int durationMinMs, int durationMaxMs)957     private void overrideStackSamplingDeviceConfigValues(boolean killswitchEnabled,
958             int durationDefaultMs, int durationMinMs, int durationMaxMs) throws Exception {
959         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_BOOL, DeviceConfigHelper.NAMESPACE,
960                 DeviceConfigHelper.KILLSWITCH_STACK_SAMPLING, killswitchEnabled);
961         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
962                 DeviceConfigHelper.STACK_SAMPLING_DURATION_MS_DEFAULT, durationDefaultMs);
963         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
964                 DeviceConfigHelper.STACK_SAMPLING_DURATION_MS_MIN, durationMinMs);
965         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
966                 DeviceConfigHelper.STACK_SAMPLING_DURATION_MS_MAX, durationMaxMs);
967     }
968 
overrideSystemTraceDeviceConfigValues(boolean killswitchEnabled, int durationDefaultMs, int durationMinMs, int durationMaxMs)969     private void overrideSystemTraceDeviceConfigValues(boolean killswitchEnabled,
970             int durationDefaultMs, int durationMinMs, int durationMaxMs) throws Exception {
971         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_BOOL, DeviceConfigHelper.NAMESPACE,
972                 DeviceConfigHelper.KILLSWITCH_SYSTEM_TRACE, killswitchEnabled);
973         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
974                 DeviceConfigHelper.SYSTEM_TRACE_DURATION_MS_DEFAULT, durationDefaultMs);
975         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
976                 DeviceConfigHelper.SYSTEM_TRACE_DURATION_MS_MIN, durationMinMs);
977         executeShellCmd(COMMAND_OVERRIDE_DEVICE_CONFIG_INT, DeviceConfigHelper.NAMESPACE,
978                 DeviceConfigHelper.SYSTEM_TRACE_DURATION_MS_MAX, durationMaxMs);
979     }
980 
981     @FormatMethod
executeShellCmd(String cmdFormat, Object... args)982     private String executeShellCmd(String cmdFormat, Object... args) throws Exception {
983         String cmd = String.format(cmdFormat, args);
984         return SystemUtil.runShellCommand(mInstrumentation, cmd);
985     }
986 
sleep(long ms)987     private static void sleep(long ms) {
988         try {
989             Thread.sleep(ms);
990         } catch (InterruptedException e) {
991             // Do nothing.
992         }
993     }
994 
995     public static class AppCallback implements Consumer<ProfilingResult> {
996 
997         public ProfilingResult mResult;
998 
999         @Override
accept(ProfilingResult result)1000         public void accept(ProfilingResult result) {
1001             mResult = result;
1002         }
1003     }
1004 
1005     // Starts a thread that keeps a CPU busy.
1006     private static class BusyLoopThread {
1007         private Thread thread;
1008         private AtomicBoolean done = new AtomicBoolean(false);
1009 
BusyLoopThread()1010         public BusyLoopThread() {
1011             done.set(false);
1012             thread = new Thread(() -> {
1013                 while (!done.get()) {
1014                 }
1015             });
1016             thread.start();
1017         }
1018 
stop()1019         public void stop() {
1020             done.set(true);
1021             try {
1022                 thread.join();
1023             } catch (InterruptedException e) {
1024                 throw new AssertionError("InterruptedException", e);
1025             }
1026         }
1027     }
1028 }
1029