Initial Contribution
diff --git a/tools/preload/Proc.java b/tools/preload/Proc.java
new file mode 100644
index 0000000..0b27a51
--- /dev/null
+++ b/tools/preload/Proc.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Collections;
+import java.util.TreeSet;
+import java.io.Serializable;
+
+/**
+ * A Dalvik process.
+ */
+class Proc implements Serializable {
+
+ private static final long serialVersionUID = 0;
+
+ /**
+ * Default percentage of time to cut off of app class loading times.
+ */
+ static final int PERCENTAGE_TO_PRELOAD = 75;
+
+ /**
+ * Maximum number of classes to preload for a given process.
+ */
+ static final int MAX_TO_PRELOAD = 100;
+
+ /** Name of system server process. */
+ private static final String SYSTEM_SERVER = "system_server";
+
+ /** Names of non-application processes. */
+ private static final Set<String> NOT_FROM_ZYGOTE
+ = new HashSet<String>(Arrays.asList(
+ "zygote",
+ "dexopt",
+ "unknown",
+ SYSTEM_SERVER,
+ "com.android.development",
+ "app_process" // am
+ ));
+
+ /** Long running services. */
+ private static final Set<String> SERVICES
+ = new HashSet<String>(Arrays.asList(
+ SYSTEM_SERVER,
+ "com.android.home",
+// Commented out to make sure DefaultTimeZones gets preloaded.
+// "com.android.phone",
+ "com.google.process.content",
+ "com.android.process.media"
+ ));
+
+ /**
+ * Classes which we shouldn't load from the Zygote.
+ */
+ static final Set<String> EXCLUDED_CLASSES
+ = new HashSet<String>(Arrays.asList(
+ // Binders
+ "android.app.AlarmManager",
+ "android.app.SearchManager",
+ "android.os.FileObserver",
+ "com.android.server.PackageManagerService$AppDirObserver",
+
+ // Threads
+ "java.lang.ProcessManager",
+
+ // This class was deleted.
+ "java.math.Elementary"
+ ));
+
+ /** Parent process. */
+ final Proc parent;
+
+ /** Process ID. */
+ final int id;
+
+ /**
+ * Name of this process. We may not have the correct name at first, i.e.
+ * some classes could have been loaded before the process name was set.
+ */
+ String name;
+
+ /** Child processes. */
+ final List<Proc> children = new ArrayList<Proc>();
+
+ /** Maps thread ID to operation stack. */
+ transient final Map<Integer, LinkedList<Operation>> stacks
+ = new HashMap<Integer, LinkedList<Operation>>();
+
+ /** Number of operations. */
+ int operationCount;
+
+ /** Sequential list of operations that happened in this process. */
+ final List<Operation> operations = new ArrayList<Operation>();
+
+ /** List of past process names. */
+ final List<String> nameHistory = new ArrayList<String>();
+
+ /** Constructs a new process. */
+ Proc(Proc parent, int id) {
+ this.parent = parent;
+ this.id = id;
+ }
+
+ /** Sets name of this process. */
+ void setName(String name) {
+ if (!name.equals(this.name)) {
+ if (this.name != null) {
+ nameHistory.add(this.name);
+ }
+ this.name = name;
+ }
+ }
+
+ /**
+ * Returns the percentage of time we should cut by preloading for this
+ * app.
+ */
+ int percentageToPreload() {
+ return PERCENTAGE_TO_PRELOAD;
+ }
+
+ /**
+ * Is this a long running process?
+ */
+ boolean isService() {
+ return SERVICES.contains(this.name);
+ }
+
+ /**
+ * Returns a list of classes which should be preloaded.
+ */
+ List<LoadedClass> highestRankedClasses() {
+ if (NOT_FROM_ZYGOTE.contains(this.name)) {
+ return Collections.emptyList();
+ }
+
+ // Sort by rank.
+ Operation[] ranked = new Operation[operations.size()];
+ ranked = operations.toArray(ranked);
+ Arrays.sort(ranked, new ClassRank());
+
+ // The percentage of time to save by preloading.
+ int timeToSave = totalTimeMicros() * percentageToPreload() / 100;
+ int timeSaved = 0;
+
+ boolean service = isService();
+
+ List<LoadedClass> highest = new ArrayList<LoadedClass>();
+ for (Operation operation : ranked) {
+ if (highest.size() >= MAX_TO_PRELOAD) {
+ System.out.println(name + " got "
+ + (timeSaved * 100 / timeToSave) + "% through");
+
+ break;
+ }
+
+ if (timeSaved >= timeToSave) {
+ break;
+ }
+
+ if (EXCLUDED_CLASSES.contains(operation.loadedClass.name)
+ || !operation.loadedClass.systemClass) {
+ continue;
+ }
+
+ // Only load java.* class for services.
+ if (!service || operation.loadedClass.name.startsWith("java.")) {
+ highest.add(operation.loadedClass);
+ }
+
+ // For services, still count the time even if it's not in java.*
+ timeSaved += operation.medianExclusiveTimeMicros();
+ }
+
+ return highest;
+ }
+
+ /**
+ * Total time spent class loading and initializing.
+ */
+ int totalTimeMicros() {
+ int totalTime = 0;
+ for (Operation operation : operations) {
+ totalTime += operation.medianExclusiveTimeMicros();
+ }
+ return totalTime;
+ }
+
+ /** Returns true if this process is an app. */
+ public boolean isApplication() {
+ return !NOT_FROM_ZYGOTE.contains(name);
+ }
+
+ /**
+ * Starts an operation.
+ *
+ * @param threadId thread the operation started in
+ * @param loadedClass class operation happened to
+ * @param time the operation started
+ */
+ void startOperation(int threadId, LoadedClass loadedClass, long time,
+ Operation.Type type) {
+ Operation o = new Operation(
+ this, loadedClass, time, operationCount++, type);
+ operations.add(o);
+
+ LinkedList<Operation> stack = stacks.get(threadId);
+ if (stack == null) {
+ stack = new LinkedList<Operation>();
+ stacks.put(threadId, stack);
+ }
+
+ if (!stack.isEmpty()) {
+ stack.getLast().subops.add(o);
+ }
+
+ stack.add(o);
+ }
+
+ /**
+ * Ends an operation.
+ *
+ * @param threadId thread the operation ended in
+ * @param loadedClass class operation happened to
+ * @param time the operation ended
+ */
+ Operation endOperation(int threadId, String className,
+ LoadedClass loadedClass, long time) {
+ LinkedList<Operation> stack = stacks.get(threadId);
+
+ if (stack == null || stack.isEmpty()) {
+ didNotStart(className);
+ return null;
+ }
+
+ Operation o = stack.getLast();
+ if (loadedClass != o.loadedClass) {
+ didNotStart(className);
+ return null;
+ }
+
+ stack.removeLast();
+
+ o.endTimeNanos = time;
+ return o;
+ }
+
+ /**
+ * Prints an error indicating that we saw the end of an operation but not
+ * the start. A bug in the logging framework which results in dropped logs
+ * causes this.
+ */
+ private static void didNotStart(String name) {
+ System.err.println("Warning: An operation ended on " + name
+ + " but it never started!");
+ }
+
+ /**
+ * Prints this process tree to stdout.
+ */
+ void print() {
+ print("");
+ }
+
+ /**
+ * Prints a child proc to standard out.
+ */
+ private void print(String prefix) {
+ System.out.println(prefix + "id=" + id + ", name=" + name);
+ for (Proc child : children) {
+ child.print(prefix + " ");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+}