Proc.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
1/* 2 * Copyright (C) 2008 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 17import java.util.Set; 18import java.util.HashSet; 19import java.util.Arrays; 20import java.util.List; 21import java.util.ArrayList; 22import java.util.LinkedList; 23import java.util.Map; 24import java.util.HashMap; 25import java.util.Collections; 26import java.util.TreeSet; 27import java.io.Serializable; 28 29/** 30 * A Dalvik process. 31 */ 32class Proc implements Serializable { 33 34 private static final long serialVersionUID = 0; 35 36 /** 37 * Default percentage of time to cut off of app class loading times. 38 */ 39 static final int PERCENTAGE_TO_PRELOAD = 75; 40 41 /** 42 * Maximum number of classes to preload for a given process. 43 */ 44 static final int MAX_TO_PRELOAD = 100; 45 46 /** Name of system server process. */ 47 private static final String SYSTEM_SERVER = "system_server"; 48 49 /** Names of non-application processes. */ 50 private static final Set<String> NOT_FROM_ZYGOTE 51 = new HashSet<String>(Arrays.asList( 52 "zygote", 53 "dexopt", 54 "unknown", 55 SYSTEM_SERVER, 56 "com.android.development", 57 "app_process" // am 58 )); 59 60 /** Long running services. */ 61 private static final Set<String> SERVICES 62 = new HashSet<String>(Arrays.asList( 63 SYSTEM_SERVER, 64 "com.android.home", 65// Commented out to make sure DefaultTimeZones gets preloaded. 66// "com.android.phone", 67 "com.google.process.content", 68 "com.android.process.media" 69 )); 70 71 /** 72 * Classes which we shouldn't load from the Zygote. 73 */ 74 static final Set<String> EXCLUDED_CLASSES 75 = new HashSet<String>(Arrays.asList( 76 // Binders 77 "android.app.AlarmManager", 78 "android.app.SearchManager", 79 "android.os.FileObserver", 80 "com.android.server.PackageManagerService$AppDirObserver", 81 82 // Threads 83 "java.lang.ProcessManager", 84 85 // This class was deleted. 86 "java.math.Elementary" 87 )); 88 89 /** Parent process. */ 90 final Proc parent; 91 92 /** Process ID. */ 93 final int id; 94 95 /** 96 * Name of this process. We may not have the correct name at first, i.e. 97 * some classes could have been loaded before the process name was set. 98 */ 99 String name; 100 101 /** Child processes. */ 102 final List<Proc> children = new ArrayList<Proc>(); 103 104 /** Maps thread ID to operation stack. */ 105 transient final Map<Integer, LinkedList<Operation>> stacks 106 = new HashMap<Integer, LinkedList<Operation>>(); 107 108 /** Number of operations. */ 109 int operationCount; 110 111 /** Sequential list of operations that happened in this process. */ 112 final List<Operation> operations = new ArrayList<Operation>(); 113 114 /** List of past process names. */ 115 final List<String> nameHistory = new ArrayList<String>(); 116 117 /** Constructs a new process. */ 118 Proc(Proc parent, int id) { 119 this.parent = parent; 120 this.id = id; 121 } 122 123 /** Sets name of this process. */ 124 void setName(String name) { 125 if (!name.equals(this.name)) { 126 if (this.name != null) { 127 nameHistory.add(this.name); 128 } 129 this.name = name; 130 } 131 } 132 133 /** 134 * Returns the percentage of time we should cut by preloading for this 135 * app. 136 */ 137 int percentageToPreload() { 138 return PERCENTAGE_TO_PRELOAD; 139 } 140 141 /** 142 * Is this a long running process? 143 */ 144 boolean isService() { 145 return SERVICES.contains(this.name); 146 } 147 148 /** 149 * Returns a list of classes which should be preloaded. 150 */ 151 List<LoadedClass> highestRankedClasses() { 152 if (NOT_FROM_ZYGOTE.contains(this.name)) { 153 return Collections.emptyList(); 154 } 155 156 // Sort by rank. 157 Operation[] ranked = new Operation[operations.size()]; 158 ranked = operations.toArray(ranked); 159 Arrays.sort(ranked, new ClassRank()); 160 161 // The percentage of time to save by preloading. 162 int timeToSave = totalTimeMicros() * percentageToPreload() / 100; 163 int timeSaved = 0; 164 165 boolean service = isService(); 166 167 List<LoadedClass> highest = new ArrayList<LoadedClass>(); 168 for (Operation operation : ranked) { 169 if (highest.size() >= MAX_TO_PRELOAD) { 170 System.out.println(name + " got " 171 + (timeSaved * 100 / timeToSave) + "% through"); 172 173 break; 174 } 175 176 if (timeSaved >= timeToSave) { 177 break; 178 } 179 180 if (EXCLUDED_CLASSES.contains(operation.loadedClass.name) 181 || !operation.loadedClass.systemClass) { 182 continue; 183 } 184 185 // Only load java.* class for services. 186 if (!service || operation.loadedClass.name.startsWith("java.")) { 187 highest.add(operation.loadedClass); 188 } 189 190 // For services, still count the time even if it's not in java.* 191 timeSaved += operation.medianExclusiveTimeMicros(); 192 } 193 194 return highest; 195 } 196 197 /** 198 * Total time spent class loading and initializing. 199 */ 200 int totalTimeMicros() { 201 int totalTime = 0; 202 for (Operation operation : operations) { 203 totalTime += operation.medianExclusiveTimeMicros(); 204 } 205 return totalTime; 206 } 207 208 /** Returns true if this process is an app. */ 209 public boolean isApplication() { 210 return !NOT_FROM_ZYGOTE.contains(name); 211 } 212 213 /** 214 * Starts an operation. 215 * 216 * @param threadId thread the operation started in 217 * @param loadedClass class operation happened to 218 * @param time the operation started 219 */ 220 void startOperation(int threadId, LoadedClass loadedClass, long time, 221 Operation.Type type) { 222 Operation o = new Operation( 223 this, loadedClass, time, operationCount++, type); 224 operations.add(o); 225 226 LinkedList<Operation> stack = stacks.get(threadId); 227 if (stack == null) { 228 stack = new LinkedList<Operation>(); 229 stacks.put(threadId, stack); 230 } 231 232 if (!stack.isEmpty()) { 233 stack.getLast().subops.add(o); 234 } 235 236 stack.add(o); 237 } 238 239 /** 240 * Ends an operation. 241 * 242 * @param threadId thread the operation ended in 243 * @param loadedClass class operation happened to 244 * @param time the operation ended 245 */ 246 Operation endOperation(int threadId, String className, 247 LoadedClass loadedClass, long time) { 248 LinkedList<Operation> stack = stacks.get(threadId); 249 250 if (stack == null || stack.isEmpty()) { 251 didNotStart(className); 252 return null; 253 } 254 255 Operation o = stack.getLast(); 256 if (loadedClass != o.loadedClass) { 257 didNotStart(className); 258 return null; 259 } 260 261 stack.removeLast(); 262 263 o.endTimeNanos = time; 264 return o; 265 } 266 267 /** 268 * Prints an error indicating that we saw the end of an operation but not 269 * the start. A bug in the logging framework which results in dropped logs 270 * causes this. 271 */ 272 private static void didNotStart(String name) { 273 System.err.println("Warning: An operation ended on " + name 274 + " but it never started!"); 275 } 276 277 /** 278 * Prints this process tree to stdout. 279 */ 280 void print() { 281 print(""); 282 } 283 284 /** 285 * Prints a child proc to standard out. 286 */ 287 private void print(String prefix) { 288 System.out.println(prefix + "id=" + id + ", name=" + name); 289 for (Proc child : children) { 290 child.print(prefix + " "); 291 } 292 } 293 294 @Override 295 public String toString() { 296 return this.name; 297 } 298} 299