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