xref: /aosp_15_r20/art/test/595-profile-saving/src/Main.java (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1*795d594fSAndroid Build Coastguard Worker /*
2*795d594fSAndroid Build Coastguard Worker  * Copyright (C) 2016 The Android Open Source Project
3*795d594fSAndroid Build Coastguard Worker  *
4*795d594fSAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*795d594fSAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*795d594fSAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*795d594fSAndroid Build Coastguard Worker  *
8*795d594fSAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*795d594fSAndroid Build Coastguard Worker  *
10*795d594fSAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*795d594fSAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*795d594fSAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*795d594fSAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*795d594fSAndroid Build Coastguard Worker  * limitations under the License.
15*795d594fSAndroid Build Coastguard Worker  */
16*795d594fSAndroid Build Coastguard Worker 
17*795d594fSAndroid Build Coastguard Worker import java.io.File;
18*795d594fSAndroid Build Coastguard Worker import java.io.IOException;
19*795d594fSAndroid Build Coastguard Worker import java.lang.reflect.Method;
20*795d594fSAndroid Build Coastguard Worker import java.time.Duration;
21*795d594fSAndroid Build Coastguard Worker 
22*795d594fSAndroid Build Coastguard Worker public class Main {
23*795d594fSAndroid Build Coastguard Worker 
main(String[] args)24*795d594fSAndroid Build Coastguard Worker   public static void main(String[] args) throws Exception {
25*795d594fSAndroid Build Coastguard Worker     System.loadLibrary(args[0]);
26*795d594fSAndroid Build Coastguard Worker 
27*795d594fSAndroid Build Coastguard Worker     if (!hasJit()) {
28*795d594fSAndroid Build Coastguard Worker       // Test requires JIT for creating profiling infos.
29*795d594fSAndroid Build Coastguard Worker       return;
30*795d594fSAndroid Build Coastguard Worker     }
31*795d594fSAndroid Build Coastguard Worker 
32*795d594fSAndroid Build Coastguard Worker     // Register `file2` with an empty jar. Even though `file2` is registered before `file`, the
33*795d594fSAndroid Build Coastguard Worker     // runtime should not write bootclasspath methods to `file2`, and it should not even create
34*795d594fSAndroid Build Coastguard Worker     // `file2`.
35*795d594fSAndroid Build Coastguard Worker     File file2 = createTempFile();
36*795d594fSAndroid Build Coastguard Worker     file2.deleteOnExit();
37*795d594fSAndroid Build Coastguard Worker     String emptyJarPath =
38*795d594fSAndroid Build Coastguard Worker             System.getenv("DEX_LOCATION") + "/res/art-gtest-jars-MainEmptyUncompressed.jar";
39*795d594fSAndroid Build Coastguard Worker     VMRuntime.registerAppInfo("test.app", file2.getPath(), file2.getPath(),
40*795d594fSAndroid Build Coastguard Worker             new String[] {emptyJarPath}, VMRuntime.CODE_PATH_TYPE_SPLIT_APK);
41*795d594fSAndroid Build Coastguard Worker 
42*795d594fSAndroid Build Coastguard Worker     File file = createTempFile();
43*795d594fSAndroid Build Coastguard Worker     file.deleteOnExit();
44*795d594fSAndroid Build Coastguard Worker     String codePath = System.getenv("DEX_LOCATION") + "/595-profile-saving.jar";
45*795d594fSAndroid Build Coastguard Worker     VMRuntime.registerAppInfo("test.app", file.getPath(), file.getPath(), new String[] {codePath},
46*795d594fSAndroid Build Coastguard Worker             VMRuntime.CODE_PATH_TYPE_PRIMARY_APK);
47*795d594fSAndroid Build Coastguard Worker 
48*795d594fSAndroid Build Coastguard Worker     File file3 = createTempFile();
49*795d594fSAndroid Build Coastguard Worker     file3.deleteOnExit();
50*795d594fSAndroid Build Coastguard Worker     String dexPath = System.getenv("DEX_LOCATION") + "/res/art-gtest-jars-Main.dex";
51*795d594fSAndroid Build Coastguard Worker     VMRuntime.registerAppInfo("test.app", file3.getPath(), file3.getPath(), new String[] {dexPath},
52*795d594fSAndroid Build Coastguard Worker             VMRuntime.CODE_PATH_TYPE_SPLIT_APK);
53*795d594fSAndroid Build Coastguard Worker 
54*795d594fSAndroid Build Coastguard Worker     // Delete the files so that we can check if the runtime creates them. The runtime should
55*795d594fSAndroid Build Coastguard Worker     // create `file` and `file3` but not `file2`.
56*795d594fSAndroid Build Coastguard Worker     file.delete();
57*795d594fSAndroid Build Coastguard Worker     file2.delete();
58*795d594fSAndroid Build Coastguard Worker     file3.delete();
59*795d594fSAndroid Build Coastguard Worker 
60*795d594fSAndroid Build Coastguard Worker     // Test that the runtime saves the profiling info of an app method in a .jar file.
61*795d594fSAndroid Build Coastguard Worker     Method appMethod =
62*795d594fSAndroid Build Coastguard Worker             Main.class.getDeclaredMethod("testAddMethodToProfile", File.class, Method.class);
63*795d594fSAndroid Build Coastguard Worker     testAddMethodToProfile(file, appMethod);
64*795d594fSAndroid Build Coastguard Worker 
65*795d594fSAndroid Build Coastguard Worker     // Test that the runtime saves the profiling info of an app method in a .dex file.
66*795d594fSAndroid Build Coastguard Worker     ClassLoader dexClassLoader = (ClassLoader) Class.forName("dalvik.system.PathClassLoader")
67*795d594fSAndroid Build Coastguard Worker                                          .getDeclaredConstructor(String.class, ClassLoader.class)
68*795d594fSAndroid Build Coastguard Worker                                          .newInstance(dexPath, null /* parent */);
69*795d594fSAndroid Build Coastguard Worker     Class<?> c = Class.forName("Main", true /* initialize */, dexClassLoader);
70*795d594fSAndroid Build Coastguard Worker     Method methodInDex = c.getMethod("main", (new String[0]).getClass());
71*795d594fSAndroid Build Coastguard Worker     testAddMethodToProfile(file3, methodInDex);
72*795d594fSAndroid Build Coastguard Worker 
73*795d594fSAndroid Build Coastguard Worker     // Test that the runtime saves the profiling info of a bootclasspath method.
74*795d594fSAndroid Build Coastguard Worker     Method bootMethod = File.class.getDeclaredMethod("exists");
75*795d594fSAndroid Build Coastguard Worker     if (bootMethod.getDeclaringClass().getClassLoader() != Object.class.getClassLoader()) {
76*795d594fSAndroid Build Coastguard Worker         System.out.println("Class loader does not match boot class");
77*795d594fSAndroid Build Coastguard Worker     }
78*795d594fSAndroid Build Coastguard Worker     testAddMethodToProfile(file, bootMethod);
79*795d594fSAndroid Build Coastguard Worker 
80*795d594fSAndroid Build Coastguard Worker     // We never expect System.console to be executed before Main.main gets invoked, and therefore
81*795d594fSAndroid Build Coastguard Worker     // it should never be in a profile.
82*795d594fSAndroid Build Coastguard Worker     Method bootNotInProfileMethod = System.class.getDeclaredMethod("console");
83*795d594fSAndroid Build Coastguard Worker     testMethodNotInProfile(file, bootNotInProfileMethod);
84*795d594fSAndroid Build Coastguard Worker 
85*795d594fSAndroid Build Coastguard Worker     testProfileNotExist(file2);
86*795d594fSAndroid Build Coastguard Worker 
87*795d594fSAndroid Build Coastguard Worker     if (!isForBootImage(file.getPath())) {
88*795d594fSAndroid Build Coastguard Worker         throw new Error("Expected profile to be for boot image");
89*795d594fSAndroid Build Coastguard Worker     }
90*795d594fSAndroid Build Coastguard Worker 
91*795d594fSAndroid Build Coastguard Worker     // Test that:
92*795d594fSAndroid Build Coastguard Worker     // 1. The runtime always writes to disk upon a forced save, even if there is nothing to update.
93*795d594fSAndroid Build Coastguard Worker     // 2. The profile for the primary APK is always the last one to write.
94*795d594fSAndroid Build Coastguard Worker     // The checks may yield false negatives. Repeat multiple times to reduce the chance of false
95*795d594fSAndroid Build Coastguard Worker     // negatives.
96*795d594fSAndroid Build Coastguard Worker     for (int i = 0; i < 10; i++) {
97*795d594fSAndroid Build Coastguard Worker         Duration primaryTimestampBefore = getMTime(file.getPath());
98*795d594fSAndroid Build Coastguard Worker         Duration splitTimestampBefore = getMTime(file3.getPath());
99*795d594fSAndroid Build Coastguard Worker         ensureProfileProcessing();
100*795d594fSAndroid Build Coastguard Worker         Duration primaryTimestampAfter = getMTime(file.getPath());
101*795d594fSAndroid Build Coastguard Worker         Duration splitTimestampAfter = getMTime(file3.getPath());
102*795d594fSAndroid Build Coastguard Worker 
103*795d594fSAndroid Build Coastguard Worker         if (primaryTimestampAfter.compareTo(primaryTimestampBefore) <= 0) {
104*795d594fSAndroid Build Coastguard Worker             throw new Error(
105*795d594fSAndroid Build Coastguard Worker                     String.format("Profile for primary APK not updated (before: %d, after: %d)",
106*795d594fSAndroid Build Coastguard Worker                             primaryTimestampBefore.toNanos(), primaryTimestampAfter.toNanos()));
107*795d594fSAndroid Build Coastguard Worker         }
108*795d594fSAndroid Build Coastguard Worker         if (splitTimestampAfter.compareTo(splitTimestampBefore) <= 0) {
109*795d594fSAndroid Build Coastguard Worker             throw new Error(
110*795d594fSAndroid Build Coastguard Worker                     String.format("Profile for split APK not updated (before: %d, after: %d)",
111*795d594fSAndroid Build Coastguard Worker                             primaryTimestampBefore.toNanos(), primaryTimestampAfter.toNanos()));
112*795d594fSAndroid Build Coastguard Worker         }
113*795d594fSAndroid Build Coastguard Worker         if (primaryTimestampAfter.compareTo(splitTimestampAfter) < 0) {
114*795d594fSAndroid Build Coastguard Worker             throw new Error(String.format(
115*795d594fSAndroid Build Coastguard Worker                     "Profile for primary APK is unexpected updated before profile for "
116*795d594fSAndroid Build Coastguard Worker                             + "split APK (primary: %d, split: %d)",
117*795d594fSAndroid Build Coastguard Worker                     primaryTimestampAfter.toNanos(), splitTimestampAfter.toNanos()));
118*795d594fSAndroid Build Coastguard Worker         }
119*795d594fSAndroid Build Coastguard Worker     }
120*795d594fSAndroid Build Coastguard Worker   }
121*795d594fSAndroid Build Coastguard Worker 
testAddMethodToProfile(File file, Method m)122*795d594fSAndroid Build Coastguard Worker   static void testAddMethodToProfile(File file, Method m) {
123*795d594fSAndroid Build Coastguard Worker     // Make sure we have a profile info for this method without the need to loop.
124*795d594fSAndroid Build Coastguard Worker     ensureProfilingInfo(m);
125*795d594fSAndroid Build Coastguard Worker     // Make sure the profile gets saved.
126*795d594fSAndroid Build Coastguard Worker     ensureProfileProcessing();
127*795d594fSAndroid Build Coastguard Worker     // Verify that the profile was saved and contains the method.
128*795d594fSAndroid Build Coastguard Worker     if (!presentInProfile(file.getPath(), m)) {
129*795d594fSAndroid Build Coastguard Worker       throw new RuntimeException("Expected method " + m + " to be in the profile");
130*795d594fSAndroid Build Coastguard Worker     }
131*795d594fSAndroid Build Coastguard Worker   }
132*795d594fSAndroid Build Coastguard Worker 
testMethodNotInProfile(File file, Method m)133*795d594fSAndroid Build Coastguard Worker   static void testMethodNotInProfile(File file, Method m) {
134*795d594fSAndroid Build Coastguard Worker     // Make sure the profile gets saved.
135*795d594fSAndroid Build Coastguard Worker     ensureProfileProcessing();
136*795d594fSAndroid Build Coastguard Worker     // Verify that the profile was saved and contains the method.
137*795d594fSAndroid Build Coastguard Worker     if (presentInProfile(file.getPath(), m)) {
138*795d594fSAndroid Build Coastguard Worker       throw new RuntimeException("Did not expect method " + m + " to be in the profile");
139*795d594fSAndroid Build Coastguard Worker     }
140*795d594fSAndroid Build Coastguard Worker   }
141*795d594fSAndroid Build Coastguard Worker 
testProfileNotExist(File file)142*795d594fSAndroid Build Coastguard Worker   static void testProfileNotExist(File file) {
143*795d594fSAndroid Build Coastguard Worker     // Make sure the profile saving has been attempted.
144*795d594fSAndroid Build Coastguard Worker     ensureProfileProcessing();
145*795d594fSAndroid Build Coastguard Worker     // Verify that the profile does not exist.
146*795d594fSAndroid Build Coastguard Worker     if (file.exists()) {
147*795d594fSAndroid Build Coastguard Worker       throw new RuntimeException("Did not expect " + file + " to exist");
148*795d594fSAndroid Build Coastguard Worker     }
149*795d594fSAndroid Build Coastguard Worker   }
150*795d594fSAndroid Build Coastguard Worker 
151*795d594fSAndroid Build Coastguard Worker   // Ensure a method has a profiling info.
ensureProfilingInfo(Method method)152*795d594fSAndroid Build Coastguard Worker   public static void ensureProfilingInfo(Method method) {
153*795d594fSAndroid Build Coastguard Worker     ensureJitBaselineCompiled(method.getDeclaringClass(), method.getName());
154*795d594fSAndroid Build Coastguard Worker   }
ensureJitBaselineCompiled(Class<?> cls, String methodName)155*795d594fSAndroid Build Coastguard Worker   public static native void ensureJitBaselineCompiled(Class<?> cls, String methodName);
156*795d594fSAndroid Build Coastguard Worker   // Ensures the profile saver does its usual processing.
ensureProfileProcessing()157*795d594fSAndroid Build Coastguard Worker   public static native void ensureProfileProcessing();
158*795d594fSAndroid Build Coastguard Worker   // Checks if the profiles saver knows about the method.
presentInProfile(String profile, Method method)159*795d594fSAndroid Build Coastguard Worker   public static native boolean presentInProfile(String profile, Method method);
160*795d594fSAndroid Build Coastguard Worker   // Returns true if the profile is for the boot image.
isForBootImage(String profile)161*795d594fSAndroid Build Coastguard Worker   public static native boolean isForBootImage(String profile);
hasJit()162*795d594fSAndroid Build Coastguard Worker   public static native boolean hasJit();
163*795d594fSAndroid Build Coastguard Worker 
164*795d594fSAndroid Build Coastguard Worker   private static final String TEMP_FILE_NAME_PREFIX = "temp";
165*795d594fSAndroid Build Coastguard Worker   private static final String TEMP_FILE_NAME_SUFFIX = "-file";
166*795d594fSAndroid Build Coastguard Worker 
getProfileInfoDump( String filename)167*795d594fSAndroid Build Coastguard Worker   static native String getProfileInfoDump(
168*795d594fSAndroid Build Coastguard Worker       String filename);
169*795d594fSAndroid Build Coastguard Worker 
createTempFile()170*795d594fSAndroid Build Coastguard Worker   private static File createTempFile() throws Exception {
171*795d594fSAndroid Build Coastguard Worker     try {
172*795d594fSAndroid Build Coastguard Worker       return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
173*795d594fSAndroid Build Coastguard Worker     } catch (IOException e) {
174*795d594fSAndroid Build Coastguard Worker       System.setProperty("java.io.tmpdir", "/data/local/tmp");
175*795d594fSAndroid Build Coastguard Worker       try {
176*795d594fSAndroid Build Coastguard Worker         return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
177*795d594fSAndroid Build Coastguard Worker       } catch (IOException e2) {
178*795d594fSAndroid Build Coastguard Worker         System.setProperty("java.io.tmpdir", "/sdcard");
179*795d594fSAndroid Build Coastguard Worker         return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
180*795d594fSAndroid Build Coastguard Worker       }
181*795d594fSAndroid Build Coastguard Worker     }
182*795d594fSAndroid Build Coastguard Worker   }
183*795d594fSAndroid Build Coastguard Worker 
getMTime(String path)184*795d594fSAndroid Build Coastguard Worker   private static Duration getMTime(String path) throws Exception {
185*795d594fSAndroid Build Coastguard Worker       // We cannot use `Files.getLastModifiedTime` because it doesn't have nanosecond precision.
186*795d594fSAndroid Build Coastguard Worker       StructTimespec st_mtim = Os.stat(path).st_mtim;
187*795d594fSAndroid Build Coastguard Worker       return Duration.ofSeconds(st_mtim.tv_sec).plus(Duration.ofNanos(st_mtim.tv_nsec));
188*795d594fSAndroid Build Coastguard Worker   }
189*795d594fSAndroid Build Coastguard Worker 
190*795d594fSAndroid Build Coastguard Worker   private static class VMRuntime {
191*795d594fSAndroid Build Coastguard Worker     public static final int CODE_PATH_TYPE_PRIMARY_APK = 1 << 0;
192*795d594fSAndroid Build Coastguard Worker     public static final int CODE_PATH_TYPE_SPLIT_APK = 1 << 1;
193*795d594fSAndroid Build Coastguard Worker     private static final Method registerAppInfoMethod;
194*795d594fSAndroid Build Coastguard Worker 
195*795d594fSAndroid Build Coastguard Worker     static {
196*795d594fSAndroid Build Coastguard Worker       try {
197*795d594fSAndroid Build Coastguard Worker         Class<? extends Object> c = Class.forName("dalvik.system.VMRuntime");
198*795d594fSAndroid Build Coastguard Worker         registerAppInfoMethod = c.getDeclaredMethod("registerAppInfo",
199*795d594fSAndroid Build Coastguard Worker             String.class, String.class, String.class, String[].class, int.class);
200*795d594fSAndroid Build Coastguard Worker       } catch (Exception e) {
201*795d594fSAndroid Build Coastguard Worker         throw new RuntimeException(e);
202*795d594fSAndroid Build Coastguard Worker       }
203*795d594fSAndroid Build Coastguard Worker     }
204*795d594fSAndroid Build Coastguard Worker 
registerAppInfo( String packageName, String curProfile, String refProfile, String[] codePaths, int codePathsType)205*795d594fSAndroid Build Coastguard Worker     public static void registerAppInfo(
206*795d594fSAndroid Build Coastguard Worker         String packageName,
207*795d594fSAndroid Build Coastguard Worker         String curProfile,
208*795d594fSAndroid Build Coastguard Worker         String refProfile,
209*795d594fSAndroid Build Coastguard Worker         String[] codePaths,
210*795d594fSAndroid Build Coastguard Worker         int codePathsType) throws Exception {
211*795d594fSAndroid Build Coastguard Worker       registerAppInfoMethod.invoke(
212*795d594fSAndroid Build Coastguard Worker           null,
213*795d594fSAndroid Build Coastguard Worker           packageName,
214*795d594fSAndroid Build Coastguard Worker           curProfile,
215*795d594fSAndroid Build Coastguard Worker           refProfile,
216*795d594fSAndroid Build Coastguard Worker           codePaths,
217*795d594fSAndroid Build Coastguard Worker           codePathsType);
218*795d594fSAndroid Build Coastguard Worker     }
219*795d594fSAndroid Build Coastguard Worker   }
220*795d594fSAndroid Build Coastguard Worker 
221*795d594fSAndroid Build Coastguard Worker   private static class Os {
stat(String path)222*795d594fSAndroid Build Coastguard Worker       public static StructStat stat(String path) throws Exception {
223*795d594fSAndroid Build Coastguard Worker           return new StructStat(Class.forName("android.system.Os")
224*795d594fSAndroid Build Coastguard Worker                           .getMethod("stat", String.class)
225*795d594fSAndroid Build Coastguard Worker                           .invoke(null, path));
226*795d594fSAndroid Build Coastguard Worker       }
227*795d594fSAndroid Build Coastguard Worker   }
228*795d594fSAndroid Build Coastguard Worker 
229*795d594fSAndroid Build Coastguard Worker   private static class StructStat {
230*795d594fSAndroid Build Coastguard Worker       public final StructTimespec st_mtim;
231*795d594fSAndroid Build Coastguard Worker 
StructStat(Object instance)232*795d594fSAndroid Build Coastguard Worker       public StructStat(Object instance) throws Exception {
233*795d594fSAndroid Build Coastguard Worker           st_mtim = new StructTimespec(instance.getClass().getField("st_mtim").get(instance));
234*795d594fSAndroid Build Coastguard Worker       }
235*795d594fSAndroid Build Coastguard Worker   }
236*795d594fSAndroid Build Coastguard Worker 
237*795d594fSAndroid Build Coastguard Worker   private static class StructTimespec {
238*795d594fSAndroid Build Coastguard Worker       public final long tv_nsec;
239*795d594fSAndroid Build Coastguard Worker       public final long tv_sec;
240*795d594fSAndroid Build Coastguard Worker 
StructTimespec(Object instance)241*795d594fSAndroid Build Coastguard Worker       public StructTimespec(Object instance) throws Exception {
242*795d594fSAndroid Build Coastguard Worker           tv_nsec = (long) instance.getClass().getField("tv_nsec").get(instance);
243*795d594fSAndroid Build Coastguard Worker           tv_sec = (long) instance.getClass().getField("tv_sec").get(instance);
244*795d594fSAndroid Build Coastguard Worker       }
245*795d594fSAndroid Build Coastguard Worker   }
246*795d594fSAndroid Build Coastguard Worker }
247