1 /*
2  * Copyright (C) 2019 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 com.android.bootimageprofile;
18 
19 import static org.junit.Assert.assertTrue;
20 
21 import com.android.tradefed.device.ITestDevice;
22 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
23 import com.android.tradefed.testtype.IDeviceTest;
24 
25 import org.junit.Test;
26 import org.junit.runner.RunWith;
27 
28 @RunWith(DeviceJUnit4ClassRunner.class)
29 public class BootImageProfileTest implements IDeviceTest {
30     private ITestDevice mTestDevice;
31     private static final String SYSTEM_SERVER_PROFILE =
32             "/data/misc/profiles/cur/0/android/primary.prof";
33     private static final boolean USE_PHENOTYPE = false;
34     private static final String DALVIK_VM_EXTRA_OPTS =
35             "-Xusejit:false -Xint -Xjitsaveprofilinginfo";
36 
37     @Override
setDevice(ITestDevice testDevice)38     public void setDevice(ITestDevice testDevice) {
39         mTestDevice = testDevice;
40     }
41 
42     @Override
getDevice()43     public ITestDevice getDevice() {
44         return mTestDevice;
45     }
46 
getProperty(String property)47     private String getProperty(String property) throws Exception {
48         if (USE_PHENOTYPE) {
49             return mTestDevice.getProperty("persist.device_config.runtime_native_boot."
50                     + property);
51         } else {
52             return mTestDevice.executeShellCommand("getprop dalvik.vm." + property).trim();
53         }
54     }
55 
setProperty(String property, String value)56     private String setProperty(String property, String value) throws Exception {
57         if (USE_PHENOTYPE) {
58             return mTestDevice.executeShellCommand(
59                 String.format("device_config put runtime_native_boot %s '%s'", property, value));
60         } else {
61             return mTestDevice.executeShellCommand(
62                 String.format("setprop dalvik.vm.%s '%s'", property, value));
63         }
64     }
65 
66     /**
67      * Validate that the boot image profile properties are set.
68      */
validateProperties()69     public void validateProperties() throws Exception {
70         String res = getProperty("profilebootclasspath");
71         assertTrue("profile boot class path not enabled: " + res, "true".equals(res));
72         res = getProperty("profilesystemserver");
73         assertTrue("profile system server not enabled: " + res, "true".equals(res));
74         res = getProperty("extra-opts");
75         assertTrue("extra options not set: " + res, DALVIK_VM_EXTRA_OPTS.equals(res));
76     }
77 
forceSaveProfile(String pkg)78     private boolean forceSaveProfile(String pkg) throws Exception {
79         String pid = mTestDevice.executeShellCommand("pidof " + pkg).trim();
80         if (pid.length() == 0) {
81             // Not yet running.
82             return false;
83         }
84         String res = mTestDevice.executeShellCommand("kill -s SIGUSR1 " + pid).trim();
85         return res.length() == 0;
86     }
87 
88     @Test
testSystemServerProfile()89     public void testSystemServerProfile() throws Exception {
90         final int numIterations = 30;
91         String res;
92         // Set properties and wait for them to be readable.
93         for (int i = 1; i <= numIterations; ++i) {
94             String pbcp = getProperty("profilebootclasspath");
95             boolean profileBootClassPath = "true".equals(pbcp);
96             String pss = getProperty("profilesystemserver");
97             boolean profileSystemServer = "true".equals(pss);
98             String extraOpts = getProperty("extra-opts");
99             boolean extraOptsOk = DALVIK_VM_EXTRA_OPTS.equals(extraOpts);
100             if (profileBootClassPath && profileSystemServer && extraOptsOk) {
101                 break;
102             }
103             if (i == numIterations) {
104                 assertTrue("profile system server not enabled: " + pss, profileSystemServer);
105                 assertTrue("profile boot class path not enabled: " + pbcp, profileBootClassPath);
106                 assertTrue("extra options not set: " + extraOpts, extraOptsOk);
107             }
108 
109             setProperty("profilebootclasspath", "true");
110             setProperty("profilesystemserver", "true");
111             setProperty("extra-opts", DALVIK_VM_EXTRA_OPTS);
112             Thread.sleep(1000);
113         }
114 
115         // Restart shell and wait for system boot.
116         res = mTestDevice.executeShellCommand("stop");
117         assertTrue("stop shell: " + res, res.length() == 0);
118         res = mTestDevice.executeShellCommand("start");
119         assertTrue("start shell: " + res, res.length() == 0);
120         for (int i = 1; i <= numIterations; ++i) {
121             String pbcp = getProperty("profilebootclasspath");
122             boolean profileBootClassPath = "true".equals(pbcp);
123             String pss = getProperty("profilesystemserver");
124             boolean profileSystemServer = "true".equals(pss);
125             String extraOpts = getProperty("extra-opts");
126             boolean extraOptsOk = DALVIK_VM_EXTRA_OPTS.equals(extraOpts);
127             if (profileBootClassPath && profileSystemServer) {
128                 break;
129             }
130             if (i == numIterations) {
131                 assertTrue("profile system server not enabled: " + pss, profileSystemServer);
132                 assertTrue("profile boot class path not enabled: " + pbcp, profileBootClassPath);
133                 assertTrue("extra options not set: " + extraOpts, extraOptsOk);
134             }
135             Thread.sleep(1000);
136         }
137 
138         // Trunacte the profile before force it to be saved to prevent previous profiles
139         // causing the test to pass.
140         res = mTestDevice.executeShellCommand("truncate -s 0 " + SYSTEM_SERVER_PROFILE).trim();
141         assertTrue(res, res.length() == 0);
142         // Wait up to 20 seconds for the profile to be saved.
143         for (int i = 1; i <= numIterations; ++i) {
144             // Force save the profile since we truncated it.
145             if (forceSaveProfile("system_server")) {
146                 // Might fail if system server is not yet running.
147                 String s = mTestDevice.executeShellCommand(
148                         "wc -c <" + SYSTEM_SERVER_PROFILE).trim();
149                 if ("0".equals(s)) {
150                     Thread.sleep(1000);
151                     continue;
152                 }
153             }
154 
155             // In case the profile is partially saved, wait an extra second.
156             Thread.sleep(1000);
157 
158             // Validate that properties are still set.
159             validateProperties();
160 
161             // Validate that the profile is non empty.
162             res = mTestDevice.executeShellCommand("profman --dump-only --profile-file="
163                     + SYSTEM_SERVER_PROFILE);
164             boolean sawFramework = false;
165             boolean sawServices = false;
166             for (String line : res.split("\n")) {
167                 if (line.contains("framework.jar")) {
168                     sawFramework = true;
169                 } else if (line.contains("framework-minus-apex.jar")) {
170                     sawFramework = true;
171                 } else if (line.contains("services.jar")) {
172                     sawServices = true;
173                 }
174             }
175             if (i == numIterations) {
176                 // Only assert for last iteration since there are race conditions where the package
177                 // manager might not be started whewn the profile saves.
178                 assertTrue("Did not see framework.jar in " + res, sawFramework);
179                 assertTrue("Did not see services.jar in " + res, sawServices);
180             }
181 
182             // Test the profile contents contain common methods for core-oj that would normally be
183             // AOT compiled. Also test that services.jar has PackageManagerService.<init> since the
184             // package manager service should always be created during boot.
185             res = mTestDevice.executeShellCommand(
186                     "profman --dump-classes-and-methods --profile-file="
187                     + SYSTEM_SERVER_PROFILE + " --apk=/apex/com.android.art/javalib/core-oj.jar"
188                     + " --apk=/system/framework/services.jar");
189             boolean sawObjectInit = false;
190             boolean sawPmInit = false;
191             for (String line : res.split("\n")) {
192                 if (line.contains("Ljava/lang/Object;-><init>()V")) {
193                     sawObjectInit = true;
194                 } else if (line.contains("Lcom/android/server/pm/PackageManagerService;-><init>")) {
195                     sawPmInit = true;
196                 }
197             }
198             if (i == numIterations) {
199                 assertTrue("Did not see Object.<init> in " + res, sawObjectInit);
200                 assertTrue("Did not see PackageManagerService.<init> in " + res, sawPmInit);
201             }
202 
203             if (sawFramework && sawServices && sawObjectInit && sawPmInit) {
204                 break;  // Asserts passed, exit.
205             }
206         }
207     }
208 }
209