1 /*
2  * Copyright (C) 2017 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.server.wm.activity;
18 
19 import static android.server.wm.ComponentNameUtils.getActivityName;
20 import static android.server.wm.ShellCommandHelper.executeShellCommand;
21 import static android.server.wm.ShellCommandHelper.executeShellCommandAndGetStdout;
22 import static android.server.wm.profileable.Components.PROFILEABLE_APP_ACTIVITY;
23 import static android.server.wm.profileable.Components.ProfileableAppActivity.COMMAND_WAIT_FOR_PROFILE_OUTPUT;
24 import static android.server.wm.profileable.Components.ProfileableAppActivity.OUTPUT_DIR;
25 import static android.server.wm.profileable.Components.ProfileableAppActivity.OUTPUT_FILE_PATH;
26 
27 import static org.hamcrest.MatcherAssert.assertThat;
28 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
29 import static org.junit.Assert.assertEquals;
30 
31 import android.content.ComponentName;
32 import android.content.Intent;
33 import android.platform.test.annotations.Presubmit;
34 import android.server.wm.ActivityManagerTestBase;
35 import android.server.wm.CommandSession.ActivitySession;
36 import android.server.wm.CommandSession.DefaultLaunchProxy;
37 
38 import org.junit.AfterClass;
39 import org.junit.BeforeClass;
40 import org.junit.Test;
41 
42 /**
43  * Build/Install/Run:
44  *     atest CtsWindowManagerDeviceActivity:AmProfileTests
45  *
46  * Please talk to Android Studio team first if you want to modify or delete these tests.
47  */
48 @Presubmit
49 public class AmProfileTests extends ActivityManagerTestBase {
50 
51     private static final String FIRST_WORD_NO_STREAMING = "*version\n";
52     private static final String FIRST_WORD_STREAMING = "SLOW";  // Magic word set by runtime.
53 
54     @BeforeClass
setUpClass()55     public static void setUpClass() {
56         // Allow ProfileableAppActivity to monitor the path.
57         executeShellCommand("mkdir -m 777 -p " + OUTPUT_DIR);
58     }
59 
60     @AfterClass
tearDownClass()61     public static void tearDownClass() {
62         executeShellCommand("rm -rf " + OUTPUT_DIR);
63     }
64 
65     /**
66      * Test am profile functionality with the following 3 configurable options:
67      *    starting the activity before start profiling? yes;
68      *    sampling-based profiling? no;
69      *    using streaming output mode? no.
70      */
71     @Test
testAmProfileStartNoSamplingNoStreaming()72     public void testAmProfileStartNoSamplingNoStreaming() throws Exception {
73         // am profile start ... , and the same to the following 3 test methods.
74         testProfile(true, false, false);
75     }
76 
77     /**
78      * The following tests are similar to testAmProfileStartNoSamplingNoStreaming(),
79      * only different in the three configuration options.
80      */
81     @Test
testAmProfileStartNoSamplingStreaming()82     public void testAmProfileStartNoSamplingStreaming() throws Exception {
83         testProfile(true, false, true);
84     }
85 
86     @Test
testAmProfileStartSamplingNoStreaming()87     public void testAmProfileStartSamplingNoStreaming() throws Exception {
88         testProfile(true, true, false);
89     }
90 
91     @Test
testAmProfileStartSamplingStreaming()92     public void testAmProfileStartSamplingStreaming() throws Exception {
93         testProfile(true, true, true);
94     }
95 
96     @Test
testAmStartStartProfilerNoSamplingNoStreaming()97     public void testAmStartStartProfilerNoSamplingNoStreaming() throws Exception {
98         // am start --start-profiler ..., and the same to the following 3 test methods.
99         testProfile(false, false, false);
100     }
101 
102     @Test
testAmStartStartProfilerNoSamplingStreaming()103     public void testAmStartStartProfilerNoSamplingStreaming() throws Exception {
104         testProfile(false, false, true);
105     }
106 
107     @Test
testAmStartStartProfilerSamplingNoStreaming()108     public void testAmStartStartProfilerSamplingNoStreaming() throws Exception {
109         testProfile(false, true, false);
110     }
111 
112     @Test
testAmStartStartProfilerSamplingStreaming()113     public void testAmStartStartProfilerSamplingStreaming() throws Exception {
114         testProfile(false, true, true);
115     }
116 
testProfile(final boolean startActivityFirst, final boolean sampling, final boolean streaming)117     private void testProfile(final boolean startActivityFirst, final boolean sampling,
118             final boolean streaming) throws Exception {
119         final ActivitySession activitySession;
120         if (startActivityFirst) {
121             activitySession = createManagedActivityClientSession().startActivity(
122                     new Intent().setComponent(PROFILEABLE_APP_ACTIVITY));
123             startProfiling(PROFILEABLE_APP_ACTIVITY.getPackageName(), sampling, streaming);
124         } else {
125             activitySession = startActivityProfiling(PROFILEABLE_APP_ACTIVITY, sampling, streaming);
126         }
127 
128         // Go to home screen and then warm start the activity to generate some interesting trace.
129         launchHomeActivity();
130         launchActivity(PROFILEABLE_APP_ACTIVITY);
131 
132         executeShellCommand(getStopProfileCmd(PROFILEABLE_APP_ACTIVITY));
133 
134         activitySession.sendCommandAndWaitReply(COMMAND_WAIT_FOR_PROFILE_OUTPUT);
135         verifyOutputFileFormat(streaming);
136     }
137 
138     /** Starts profiler on a started process. */
startProfiling(String processName, boolean sampling, boolean streaming)139     private static void startProfiling(String processName, boolean sampling, boolean streaming) {
140         final StringBuilder builder = new StringBuilder("am profile start");
141         appendProfileParameters(builder, sampling, streaming);
142         builder.append(String.format(" %s %s", processName, OUTPUT_FILE_PATH));
143         executeShellCommand(builder.toString());
144     }
145 
146     /** Starts the activity with profiler. */
startActivityProfiling(ComponentName activityName, boolean sampling, boolean streaming)147     private ActivitySession startActivityProfiling(ComponentName activityName, boolean sampling,
148             boolean streaming) {
149         return createManagedActivityClientSession().startActivity(new DefaultLaunchProxy() {
150 
151             @Override
152             public boolean shouldWaitForLaunched() {
153                 // The shell command included "-W".
154                 return false;
155             }
156 
157             @Override
158             public void execute() {
159                 final StringBuilder builder = new StringBuilder();
160                 builder.append(String.format("am start -n %s -W -S --start-profiler %s",
161                         getActivityName(activityName), OUTPUT_FILE_PATH));
162                 appendProfileParameters(builder, sampling, streaming);
163                 mLaunchInjector.setupShellCommand(builder);
164                 executeShellCommand(builder.toString());
165             }
166         });
167     }
168 
169     private static void appendProfileParameters(StringBuilder builder, boolean sampling,
170             boolean streaming) {
171         if (sampling) {
172             builder.append(" --sampling 1000");
173         }
174         if (streaming) {
175             builder.append(" --streaming");
176         }
177     }
178 
179     private static String getStopProfileCmd(final ComponentName activityName) {
180         return "am profile stop " + activityName.getPackageName();
181     }
182 
183     private void verifyOutputFileFormat(final boolean streaming) throws Exception {
184         // This is a hack. The am service has to write to /data/local/tmp because it doesn't have
185         // access to the sdcard. The test cannot read from /data/local/tmp. This allows us to
186         // scan the content to validate what is needed for this test.
187         final String firstLine = executeShellCommandAndGetStdout("head -1 " + OUTPUT_FILE_PATH);
188 
189         final String expectedFirstWord = streaming ? FIRST_WORD_STREAMING : FIRST_WORD_NO_STREAMING;
190         assertThat(
191                 "data size", firstLine.length(), greaterThanOrEqualTo(expectedFirstWord.length()));
192         final String actualFirstWord = firstLine.substring(0, expectedFirstWord.length());
193         assertEquals("Unexpected first word", expectedFirstWord, actualFirstWord);
194 
195         // Clean up.
196         executeShellCommand("rm -f " + OUTPUT_FILE_PATH);
197     }
198 }
199