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