diff options
Diffstat (limited to 'tools/preload')
-rw-r--r-- | tools/preload/20080522.compiled | bin | 0 -> 11414749 bytes | |||
-rw-r--r-- | tools/preload/20090811.compiled | bin | 0 -> 15943336 bytes | |||
-rw-r--r-- | tools/preload/20100223.compiled | bin | 0 -> 21723744 bytes | |||
-rw-r--r-- | tools/preload/Android.mk | 23 | ||||
-rw-r--r-- | tools/preload/Compile.java | 73 | ||||
-rw-r--r-- | tools/preload/LoadedClass.java | 130 | ||||
-rw-r--r-- | tools/preload/MemoryUsage.java | 298 | ||||
-rw-r--r-- | tools/preload/Operation.java | 136 | ||||
-rw-r--r-- | tools/preload/Policy.java | 89 | ||||
-rw-r--r-- | tools/preload/PrintCsv.java | 127 | ||||
-rw-r--r-- | tools/preload/PrintHtmlDiff.java | 142 | ||||
-rw-r--r-- | tools/preload/PrintPsTree.java | 46 | ||||
-rw-r--r-- | tools/preload/Proc.java | 168 | ||||
-rw-r--r-- | tools/preload/Record.java | 182 | ||||
-rw-r--r-- | tools/preload/Root.java | 163 | ||||
-rw-r--r-- | tools/preload/WritePreloadedClassFile.java | 155 | ||||
-rw-r--r-- | tools/preload/loadclass/Android.mk | 9 | ||||
-rw-r--r-- | tools/preload/loadclass/LoadClass.java | 84 | ||||
-rw-r--r-- | tools/preload/preload.iml | 14 | ||||
-rw-r--r-- | tools/preload/preload.ipr | 495 | ||||
-rw-r--r-- | tools/preload/sorttable.js | 493 |
21 files changed, 2827 insertions, 0 deletions
diff --git a/tools/preload/20080522.compiled b/tools/preload/20080522.compiled Binary files differnew file mode 100644 index 0000000..a2af422 --- /dev/null +++ b/tools/preload/20080522.compiled diff --git a/tools/preload/20090811.compiled b/tools/preload/20090811.compiled Binary files differnew file mode 100644 index 0000000..6dbeca0 --- /dev/null +++ b/tools/preload/20090811.compiled diff --git a/tools/preload/20100223.compiled b/tools/preload/20100223.compiled Binary files differnew file mode 100644 index 0000000..3056388 --- /dev/null +++ b/tools/preload/20100223.compiled diff --git a/tools/preload/Android.mk b/tools/preload/Android.mk new file mode 100644 index 0000000..f325870 --- /dev/null +++ b/tools/preload/Android.mk @@ -0,0 +1,23 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + Compile.java \ + LoadedClass.java \ + MemoryUsage.java \ + Operation.java \ + Policy.java \ + PrintCsv.java \ + PrintHtmlDiff.java \ + PrintPsTree.java \ + Proc.java \ + Record.java \ + Root.java \ + WritePreloadedClassFile.java + +LOCAL_MODULE:= preload + +include $(BUILD_HOST_JAVA_LIBRARY) + +include $(call all-subdir-makefiles) diff --git a/tools/preload/Compile.java b/tools/preload/Compile.java new file mode 100644 index 0000000..67258ef --- /dev/null +++ b/tools/preload/Compile.java @@ -0,0 +1,73 @@ +/* + * 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.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +/** + * Parses and analyzes a log, pulling our PRELOAD information. If you have + * an emulator or device running in the background, this class will use it + * to measure and record the memory usage of each class. + * + * TODO: Should analyze lines and select substring dynamically (instead of hardcoded 19) + */ +public class Compile { + + public static void main(String[] args) throws IOException { + if (args.length != 2) { + System.err.println("Usage: Compile [log file] [output file]"); + System.exit(0); + } + + Root root = new Root(); + + List<Record> records = new ArrayList<Record>(); + + BufferedReader in = new BufferedReader(new InputStreamReader( + new FileInputStream(args[0]))); + + String line; + int lineNumber = 0; + while ((line = in.readLine()) != null) { + lineNumber++; + if (line.startsWith("I/PRELOAD")) { + try { + String clipped = line.substring(19); + records.add(new Record(clipped, lineNumber)); + } catch (RuntimeException e) { + throw new RuntimeException( + "Exception while recording line " + lineNumber + ": " + line, e); + } + } + } + + for (Record record : records) { + root.indexProcess(record); + } + + for (Record record : records) { + root.indexClassOperation(record); + } + + in.close(); + + root.toFile(args[1]); + } +} diff --git a/tools/preload/LoadedClass.java b/tools/preload/LoadedClass.java new file mode 100644 index 0000000..86e5dfc --- /dev/null +++ b/tools/preload/LoadedClass.java @@ -0,0 +1,130 @@ +/* + * 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.io.Serializable; +import java.util.*; + +/** + * A loaded class. + */ +class LoadedClass implements Serializable, Comparable<LoadedClass> { + + private static final long serialVersionUID = 0; + + /** Class name. */ + final String name; + + /** Load operations. */ + final List<Operation> loads = new ArrayList<Operation>(); + + /** Static initialization operations. */ + final List<Operation> initializations = new ArrayList<Operation>(); + + /** Memory usage gathered by loading only this class in its own VM. */ + MemoryUsage memoryUsage = MemoryUsage.NOT_AVAILABLE; + + /** + * Whether or not this class was loaded in the system class loader. + */ + final boolean systemClass; + + /** Whether or not this class will be preloaded. */ + boolean preloaded; + + /** Constructs a new class. */ + LoadedClass(String name, boolean systemClass) { + this.name = name; + this.systemClass = systemClass; + } + + void measureMemoryUsage() { + this.memoryUsage = MemoryUsage.forClass(name); + } + + int mlt = -1; + + /** Median time to load this class. */ + int medianLoadTimeMicros() { + if (mlt != -1) { + return mlt; + } + + return mlt = calculateMedian(loads); + } + + int mit = -1; + + /** Median time to initialize this class. */ + int medianInitTimeMicros() { + if (mit != -1) { + return mit; + } + + return mit = calculateMedian(initializations); + } + + int medianTimeMicros() { + return medianInitTimeMicros() + medianLoadTimeMicros(); + } + + /** Calculates the median duration for a list of operations. */ + private static int calculateMedian(List<Operation> operations) { + int size = operations.size(); + if (size == 0) { + return 0; + } + + int[] times = new int[size]; + for (int i = 0; i < size; i++) { + times[i] = operations.get(i).exclusiveTimeMicros(); + } + + Arrays.sort(times); + int middle = size / 2; + if (size % 2 == 1) { + // Odd + return times[middle]; + } else { + // Even -- average the two. + return (times[middle - 1] + times[middle]) / 2; + } + } + + /** Returns names of processes that loaded this class. */ + Set<String> processNames() { + Set<String> names = new HashSet<String>(); + addProcessNames(loads, names); + addProcessNames(initializations, names); + return names; + } + + private void addProcessNames(List<Operation> ops, Set<String> names) { + for (Operation operation : ops) { + if (operation.process.fromZygote()) { + names.add(operation.process.name); + } + } + } + + public int compareTo(LoadedClass o) { + return name.compareTo(o.name); + } + + @Override + public String toString() { + return name; + } +} diff --git a/tools/preload/MemoryUsage.java b/tools/preload/MemoryUsage.java new file mode 100644 index 0000000..d8f95f4 --- /dev/null +++ b/tools/preload/MemoryUsage.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.io.Serializable; +import java.io.IOException; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Memory usage information. + */ +class MemoryUsage implements Serializable { + + private static final long serialVersionUID = 0; + + static final MemoryUsage NOT_AVAILABLE = new MemoryUsage(); + + static int errorCount = 0; + + // These values are in 1kB increments (not 4kB like you'd expect). + final int nativeSharedPages; + final int javaSharedPages; + final int otherSharedPages; + final int nativePrivatePages; + final int javaPrivatePages; + final int otherPrivatePages; + + final int allocCount; + final int allocSize; + final int freedCount; + final int freedSize; + final long nativeHeapSize; + + public MemoryUsage(String line) { + String[] parsed = line.split(","); + + nativeSharedPages = Integer.parseInt(parsed[1]); + javaSharedPages = Integer.parseInt(parsed[2]); + otherSharedPages = Integer.parseInt(parsed[3]); + nativePrivatePages = Integer.parseInt(parsed[4]); + javaPrivatePages = Integer.parseInt(parsed[5]); + otherPrivatePages = Integer.parseInt(parsed[6]); + allocCount = Integer.parseInt(parsed[7]); + allocSize = Integer.parseInt(parsed[8]); + freedCount = Integer.parseInt(parsed[9]); + freedSize = Integer.parseInt(parsed[10]); + nativeHeapSize = Long.parseLong(parsed[11]); + } + + MemoryUsage() { + nativeSharedPages = -1; + javaSharedPages = -1; + otherSharedPages = -1; + nativePrivatePages = -1; + javaPrivatePages = -1; + otherPrivatePages = -1; + + allocCount = -1; + allocSize = -1; + freedCount = -1; + freedSize = -1; + nativeHeapSize = -1; + } + + MemoryUsage(int nativeSharedPages, + int javaSharedPages, + int otherSharedPages, + int nativePrivatePages, + int javaPrivatePages, + int otherPrivatePages, + int allocCount, + int allocSize, + int freedCount, + int freedSize, + long nativeHeapSize) { + this.nativeSharedPages = nativeSharedPages; + this.javaSharedPages = javaSharedPages; + this.otherSharedPages = otherSharedPages; + this.nativePrivatePages = nativePrivatePages; + this.javaPrivatePages = javaPrivatePages; + this.otherPrivatePages = otherPrivatePages; + this.allocCount = allocCount; + this.allocSize = allocSize; + this.freedCount = freedCount; + this.freedSize = freedSize; + this.nativeHeapSize = nativeHeapSize; + } + + MemoryUsage subtract(MemoryUsage baseline) { + return new MemoryUsage( + nativeSharedPages - baseline.nativeSharedPages, + javaSharedPages - baseline.javaSharedPages, + otherSharedPages - baseline.otherSharedPages, + nativePrivatePages - baseline.nativePrivatePages, + javaPrivatePages - baseline.javaPrivatePages, + otherPrivatePages - baseline.otherPrivatePages, + allocCount - baseline.allocCount, + allocSize - baseline.allocSize, + freedCount - baseline.freedCount, + freedSize - baseline.freedSize, + nativeHeapSize - baseline.nativeHeapSize); + } + + int javaHeapSize() { + return allocSize - freedSize; + } + + int totalHeap() { + return javaHeapSize() + (int) nativeHeapSize; + } + + int javaPagesInK() { + return javaSharedPages + javaPrivatePages; + } + + int nativePagesInK() { + return nativeSharedPages + nativePrivatePages; + } + int otherPagesInK() { + return otherSharedPages + otherPrivatePages; + } + + int totalPages() { + return javaSharedPages + javaPrivatePages + nativeSharedPages + + nativePrivatePages + otherSharedPages + otherPrivatePages; + } + + /** + * Was this information available? + */ + boolean isAvailable() { + return nativeSharedPages != -1; + } + + /** + * Measures baseline memory usage. + */ + static MemoryUsage baseline() { + return forClass(null); + } + + private static final String CLASS_PATH = "-Xbootclasspath" + + ":/system/framework/core.jar" + + ":/system/framework/ext.jar" + + ":/system/framework/framework.jar" + + ":/system/framework/framework-tests.jar" + + ":/system/framework/services.jar" + + ":/system/framework/loadclass.jar"; + + private static final String[] GET_DIRTY_PAGES = { + "adb", "shell", "dalvikvm", CLASS_PATH, "LoadClass" }; + + /** + * Measures memory usage for the given class. + */ + static MemoryUsage forClass(String className) { + MeasureWithTimeout measurer = new MeasureWithTimeout(className); + + new Thread(measurer).start(); + + synchronized (measurer) { + if (measurer.memoryUsage == null) { + // Wait up to 10s. + try { + measurer.wait(30000); + } catch (InterruptedException e) { + System.err.println("Interrupted waiting for measurement."); + e.printStackTrace(); + return NOT_AVAILABLE; + } + + // If it's still null. + if (measurer.memoryUsage == null) { + System.err.println("Timed out while measuring " + + className + "."); + return NOT_AVAILABLE; + } + } + + System.err.println("Got memory usage for " + className + "."); + return measurer.memoryUsage; + } + } + + static class MeasureWithTimeout implements Runnable { + + final String className; + MemoryUsage memoryUsage = null; + + MeasureWithTimeout(String className) { + this.className = className; + } + + public void run() { + MemoryUsage measured = measure(); + + synchronized (this) { + memoryUsage = measured; + notifyAll(); + } + } + + private MemoryUsage measure() { + String[] commands = GET_DIRTY_PAGES; + if (className != null) { + List<String> commandList = new ArrayList<String>( + GET_DIRTY_PAGES.length + 1); + commandList.addAll(Arrays.asList(commands)); + commandList.add(className); + commands = commandList.toArray(new String[commandList.size()]); + } + + try { + final Process process = Runtime.getRuntime().exec(commands); + + final InputStream err = process.getErrorStream(); + + // Send error output to stderr. + Thread errThread = new Thread() { + @Override + public void run() { + copy(err, System.err); + } + }; + errThread.setDaemon(true); + errThread.start(); + + BufferedReader in = new BufferedReader( + new InputStreamReader(process.getInputStream())); + String line = in.readLine(); + if (line == null || !line.startsWith("DECAFBAD,")) { + System.err.println("Got bad response for " + className + + ": " + line + "; command was " + Arrays.toString(commands)); + errorCount += 1; + return NOT_AVAILABLE; + } + + in.close(); + err.close(); + process.destroy(); + + return new MemoryUsage(line); + } catch (IOException e) { + System.err.println("Error getting stats for " + + className + "."); + e.printStackTrace(); + return NOT_AVAILABLE; + } + } + + } + + /** + * Copies from one stream to another. + */ + private static void copy(InputStream in, OutputStream out) { + byte[] buffer = new byte[1024]; + int read; + try { + while ((read = in.read(buffer)) > -1) { + out.write(buffer, 0, read); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** Measures memory usage information and stores it in the model. */ + public static void main(String[] args) throws IOException, + ClassNotFoundException { + Root root = Root.fromFile(args[0]); + root.baseline = baseline(); + for (LoadedClass loadedClass : root.loadedClasses.values()) { + if (loadedClass.systemClass) { + loadedClass.measureMemoryUsage(); + } + } + root.toFile(args[0]); + } +} diff --git a/tools/preload/Operation.java b/tools/preload/Operation.java new file mode 100644 index 0000000..4f1938e --- /dev/null +++ b/tools/preload/Operation.java @@ -0,0 +1,136 @@ +/* + * 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.List; +import java.util.ArrayList; +import java.io.Serializable; + +/** + * An operation with a duration. Could represent a class load or initialization. + */ +class Operation implements Serializable { + + private static final long serialVersionUID = 0; + + /** + * Type of operation. + */ + enum Type { + LOAD, INIT + } + + /** Process this operation occurred in. */ + final Proc process; + + /** Start time for this operation. */ + final long startTimeNanos; + + /** Index of this operation relative to its process. */ + final int index; + + /** Type of operation. */ + final Type type; + + /** End time for this operation. */ + long endTimeNanos = -1; + + /** The class that this operation loaded or initialized. */ + final LoadedClass loadedClass; + + /** Other operations that occurred during this one. */ + final List<Operation> subops = new ArrayList<Operation>(); + + /** Constructs a new operation. */ + Operation(Proc process, LoadedClass loadedClass, long startTimeNanos, + int index, Type type) { + this.process = process; + this.loadedClass = loadedClass; + this.startTimeNanos = startTimeNanos; + this.index = index; + this.type = type; + } + + /** + * Returns how long this class initialization and all the nested class + * initializations took. + */ + private long inclusiveTimeNanos() { + if (endTimeNanos == -1) { + throw new IllegalStateException("End time hasn't been set yet: " + + loadedClass.name); + } + + return endTimeNanos - startTimeNanos; + } + + /** + * Returns how long this class initialization took. + */ + int exclusiveTimeMicros() { + long exclusive = inclusiveTimeNanos(); + + for (Operation child : subops) { + exclusive -= child.inclusiveTimeNanos(); + } + + if (exclusive < 0) { + throw new AssertionError(loadedClass.name); + } + + return nanosToMicros(exclusive); + } + + /** Gets the median time that this operation took across all processes. */ + int medianExclusiveTimeMicros() { + switch (type) { + case LOAD: return loadedClass.medianLoadTimeMicros(); + case INIT: return loadedClass.medianInitTimeMicros(); + default: throw new AssertionError(); + } + } + + /** + * Converts nanoseconds to microseconds. + * + * @throws RuntimeException if overflow occurs + */ + private static int nanosToMicros(long nanos) { + long micros = nanos / 1000; + int microsInt = (int) micros; + if (microsInt != micros) { + throw new RuntimeException("Integer overflow: " + nanos); + } + return microsInt; + } + + /** + * Primarily for debugger support + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(type.toString()); + sb.append(' '); + sb.append(loadedClass.toString()); + if (subops.size() > 0) { + sb.append(" ("); + sb.append(subops.size()); + sb.append(" sub ops)"); + } + return sb.toString(); + } + +} diff --git a/tools/preload/Policy.java b/tools/preload/Policy.java new file mode 100644 index 0000000..af46820 --- /dev/null +++ b/tools/preload/Policy.java @@ -0,0 +1,89 @@ +/* + * 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.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Policy that governs which classes are preloaded. + */ +public class Policy { + + /** + * No constructor - use static methods only + */ + private Policy() {} + + /** + * This location (in the build system) of the preloaded-classes file. + */ + static final String PRELOADED_CLASS_FILE + = "frameworks/base/preloaded-classes"; + + /** + * Long running services. These are restricted in their contribution to the + * preloader because their launch time is less critical. + */ + // TODO: Generate this automatically from package manager. + private static final Set<String> SERVICES = new HashSet<String>(Arrays.asList( + "system_server", + "com.google.process.content", + "android.process.media", + "com.android.bluetooth", + "com.android.calendar", + "com.android.inputmethod.latin", + "com.android.phone", + "com.google.android.apps.maps.FriendService", // pre froyo + "com.google.android.apps.maps:FriendService", // froyo + "com.google.android.apps.maps.LocationFriendService", + "com.google.android.deskclock", + "com.google.process.gapps", + "android.tts" + )); + + /** + * Classes which we shouldn't load from the Zygote. + */ + private 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 + "android.os.AsyncTask", + "android.pim.ContactsAsyncHelper", + "android.webkit.WebViewClassic$1", + "java.lang.ProcessManager" + )); + + /** + * Returns true if the given process name is a "long running" process or + * service. + */ + public static boolean isService(String processName) { + return SERVICES.contains(processName); + } + + /** Reports if the given class should be preloaded. */ + public static boolean isPreloadable(LoadedClass clazz) { + return clazz.systemClass && !EXCLUDED_CLASSES.contains(clazz.name) + && !clazz.name.endsWith("$NoPreloadHolder"); + } +} diff --git a/tools/preload/PrintCsv.java b/tools/preload/PrintCsv.java new file mode 100644 index 0000000..1820830 --- /dev/null +++ b/tools/preload/PrintCsv.java @@ -0,0 +1,127 @@ +/* + * 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.io.IOException; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.io.BufferedInputStream; +import java.io.Writer; +import java.io.PrintStream; +import java.util.Set; +import java.util.HashSet; +import java.util.TreeSet; +import java.util.Iterator; + +/** + * Prints raw information in CSV format. + */ +public class PrintCsv { + + public static void main(String[] args) + throws IOException, ClassNotFoundException { + if (args.length != 1) { + System.err.println("Usage: PrintCsv [compiled log file]"); + System.exit(0); + } + + Root root = Root.fromFile(args[0]); + + printHeaders(System.out); + + MemoryUsage baseline = MemoryUsage.baseline(); + + for (LoadedClass loadedClass : root.loadedClasses.values()) { + if (!loadedClass.systemClass) { + continue; + } + + printRow(System.out, baseline, loadedClass); + } + } + + static void printHeaders(PrintStream out) { + out.println("Name" + + ",Preloaded" + + ",Median Load Time (us)" + + ",Median Init Time (us)" + + ",Process Names" + + ",Load Count" + + ",Init Count" + + ",Managed Heap (B)" + + ",Native Heap (B)" + + ",Managed Pages (kB)" + + ",Native Pages (kB)" + + ",Other Pages (kB)"); + } + + static void printRow(PrintStream out, MemoryUsage baseline, + LoadedClass loadedClass) { + out.print(loadedClass.name); + out.print(','); + out.print(loadedClass.preloaded); + out.print(','); + out.print(loadedClass.medianLoadTimeMicros()); + out.print(','); + out.print(loadedClass.medianInitTimeMicros()); + out.print(','); + out.print('"'); + + Set<String> procNames = new TreeSet<String>(); + for (Operation op : loadedClass.loads) + procNames.add(op.process.name); + for (Operation op : loadedClass.initializations) + procNames.add(op.process.name); + + if (procNames.size() <= 3) { + for (String name : procNames) { + out.print(name + "\n"); + } + } else { + Iterator<String> i = procNames.iterator(); + out.print(i.next() + "\n"); + out.print(i.next() + "\n"); + out.print("...and " + (procNames.size() - 2) + + " others."); + } + + out.print('"'); + out.print(','); + out.print(loadedClass.loads.size()); + out.print(','); + out.print(loadedClass.initializations.size()); + + if (loadedClass.memoryUsage.isAvailable()) { + MemoryUsage subtracted + = loadedClass.memoryUsage.subtract(baseline); + + out.print(','); + out.print(subtracted.javaHeapSize()); + out.print(','); + out.print(subtracted.nativeHeapSize); + out.print(','); + out.print(subtracted.javaPagesInK()); + out.print(','); + out.print(subtracted.nativePagesInK()); + out.print(','); + out.print(subtracted.otherPagesInK()); + + } else { + out.print(",n/a,n/a,n/a,n/a,n/a"); + } + + out.println(); + } +} diff --git a/tools/preload/PrintHtmlDiff.java b/tools/preload/PrintHtmlDiff.java new file mode 100644 index 0000000..b101c85 --- /dev/null +++ b/tools/preload/PrintHtmlDiff.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2009 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.io.IOException; +import java.io.FileReader; +import java.io.BufferedReader; +import java.io.PrintStream; +import java.util.Set; +import java.util.TreeSet; +import java.util.HashSet; +import java.util.Iterator; + +/** + * Prints HTML containing removed and added files. + */ +public class PrintHtmlDiff { + + private static final String OLD_PRELOADED_CLASSES + = "old-preloaded-classes"; + + public static void main(String[] args) throws IOException, + ClassNotFoundException { + Root root = Root.fromFile(args[0]); + + BufferedReader oldClasses = new BufferedReader( + new FileReader(OLD_PRELOADED_CLASSES)); + + // Classes loaded implicitly by the zygote. + Set<LoadedClass> zygote = new HashSet<LoadedClass>(); + for (Proc proc : root.processes.values()) { + if (proc.name.equals("zygote")) { + for (Operation op : proc.operations) { + zygote.add(op.loadedClass); + } + break; + } + } + + Set<LoadedClass> removed = new TreeSet<LoadedClass>(); + Set<LoadedClass> added = new TreeSet<LoadedClass>(); + + for (LoadedClass loadedClass : root.loadedClasses.values()) { + if (loadedClass.preloaded && !zygote.contains(loadedClass)) { + added.add(loadedClass); + } + } + + String line; + while ((line = oldClasses.readLine()) != null) { + line = line.trim(); + LoadedClass clazz = root.loadedClasses.get(line); + if (clazz != null) { + added.remove(clazz); + if (!clazz.preloaded) removed.add(clazz); + } + } + + PrintStream out = System.out; + + out.println("<html><body>"); + out.println("<style>"); + out.println("a, th, td, h2 { font-family: arial }"); + out.println("th, td { font-size: small }"); + out.println("</style>"); + out.println("<script src=\"sorttable.js\"></script>"); + out.println("<p><a href=\"#removed\">Removed</a>"); + out.println("<a name=\"added\"/><h2>Added</h2>"); + printTable(out, root.baseline, added); + out.println("<a name=\"removed\"/><h2>Removed</h2>"); + printTable(out, root.baseline, removed); + out.println("</body></html>"); + } + + static void printTable(PrintStream out, MemoryUsage baseline, + Iterable<LoadedClass> classes) { + out.println("<table border=\"1\" cellpadding=\"5\"" + + " class=\"sortable\">"); + + out.println("<thead><tr>"); + out.println("<th>Name</th>"); + out.println("<th>Load Time (us)</th>"); + out.println("<th>Loaded By</th>"); + out.println("<th>Heap (B)</th>"); + out.println("<th>Pages</th>"); + out.println("</tr></thead>"); + + for (LoadedClass clazz : classes) { + out.println("<tr>"); + out.println("<td>" + clazz.name + "</td>"); + out.println("<td>" + clazz.medianTimeMicros() + "</td>"); + + out.println("<td>"); + Set<String> procNames = new TreeSet<String>(); + for (Operation op : clazz.loads) procNames.add(op.process.name); + for (Operation op : clazz.initializations) { + procNames.add(op.process.name); + } + if (procNames.size() <= 3) { + for (String name : procNames) { + out.print(name + "<br/>"); + } + } else { + Iterator<String> i = procNames.iterator(); + out.print(i.next() + "<br/>"); + out.print(i.next() + "<br/>"); + out.print("...and " + (procNames.size() - 2) + + " others."); + } + out.println("</td>"); + + if (clazz.memoryUsage.isAvailable()) { + MemoryUsage subtracted + = clazz.memoryUsage.subtract(baseline); + + out.println("<td>" + (subtracted.javaHeapSize() + + subtracted.nativeHeapSize) + "</td>"); + out.println("<td>" + subtracted.totalPages() + "</td>"); + } else { + for (int i = 0; i < 2; i++) { + out.println("<td>n/a</td>"); + } + } + + out.println("</tr>"); + } + + out.println("</table>"); + } +} diff --git a/tools/preload/PrintPsTree.java b/tools/preload/PrintPsTree.java new file mode 100644 index 0000000..22701fa --- /dev/null +++ b/tools/preload/PrintPsTree.java @@ -0,0 +1,46 @@ +/* + * 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.io.IOException; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.io.BufferedInputStream; + +/** + * Prints raw information in CSV format. + */ +public class PrintPsTree { + + public static void main(String[] args) + throws IOException, ClassNotFoundException { + if (args.length != 1) { + System.err.println("Usage: PrintCsv [compiled log file]"); + System.exit(0); + } + + FileInputStream fin = new FileInputStream(args[0]); + ObjectInputStream oin = new ObjectInputStream( + new BufferedInputStream(fin)); + + Root root = (Root) oin.readObject(); + + for (Proc proc : root.processes.values()) { + if (proc.parent == null) { + proc.print(); + } + } + } +} diff --git a/tools/preload/Proc.java b/tools/preload/Proc.java new file mode 100644 index 0000000..2105021 --- /dev/null +++ b/tools/preload/Proc.java @@ -0,0 +1,168 @@ +/* + * 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.List; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Map; +import java.util.HashMap; +import java.io.Serializable; + +/** + * A Dalvik process. + */ +class Proc implements Serializable { + + private static final long serialVersionUID = 0; + + /** 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 true if this process comes from the zygote. + */ + public boolean fromZygote() { + return parent != null && parent.name.equals("zygote") + && !name.equals("com.android.development"); + } + + /** + * 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; + } +} diff --git a/tools/preload/Record.java b/tools/preload/Record.java new file mode 100644 index 0000000..d0a2af4 --- /dev/null +++ b/tools/preload/Record.java @@ -0,0 +1,182 @@ +/* + * 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. + */ + +/** + * One line from the loaded-classes file. + */ +class Record { + + /** + * The delimiter character we use, {@code :}, conflicts with some other + * names. In that case, manually replace the delimiter with something else. + */ + private static final String[] REPLACE_CLASSES = { + "com.google.android.apps.maps:FriendService", + "com.google.android.apps.maps\\u003AFriendService", + "com.google.android.apps.maps:driveabout", + "com.google.android.apps.maps\\u003Adriveabout", + "com.google.android.apps.maps:GoogleLocationService", + "com.google.android.apps.maps\\u003AGoogleLocationService", + "com.google.android.apps.maps:LocationFriendService", + "com.google.android.apps.maps\\u003ALocationFriendService", + "com.google.android.apps.maps:MapsBackgroundService", + "com.google.android.apps.maps\\u003AMapsBackgroundService", + "com.google.android.apps.maps:NetworkLocationService", + "com.google.android.apps.maps\\u003ANetworkLocationService", + "com.android.chrome:sandboxed_process", + "com.android.chrome\\u003Asandboxed_process", + "com.android.fakeoemfeatures:background", + "com.android.fakeoemfeatures\\u003Abackground", + "com.android.fakeoemfeatures:core", + "com.android.fakeoemfeatures\\u003Acore", + "com.android.launcher:wallpaper_chooser", + "com.android.launcher\\u003Awallpaper_chooser", + "com.android.nfc:handover", + "com.android.nfc\\u003Ahandover", + "com.google.android.music:main", + "com.google.android.music\\u003Amain", + "com.google.android.music:ui", + "com.google.android.music\\u003Aui", + "com.google.android.setupwarlock:broker", + "com.google.android.setupwarlock\\u003Abroker", + "mobi.mgeek.TunnyBrowser:DolphinNotification", + "mobi.mgeek.TunnyBrowser\\u003ADolphinNotification", + "com.qo.android.sp.oem:Quickword", + "com.qo.android.sp.oem\\u003AQuickword", + "android:ui", + "android\\u003Aui", + "system:ui", + "system\\u003Aui", + }; + + enum Type { + /** Start of initialization. */ + START_LOAD, + + /** End of initialization. */ + END_LOAD, + + /** Start of initialization. */ + START_INIT, + + /** End of initialization. */ + END_INIT + } + + /** Parent process ID. */ + final int ppid; + + /** Process ID. */ + final int pid; + + /** Thread ID. */ + final int tid; + + /** Process name. */ + final String processName; + + /** Class loader pointer. */ + final int classLoader; + + /** Type of record. */ + final Type type; + + /** Name of loaded class. */ + final String className; + + /** Record time (ns). */ + final long time; + + /** Source file line# */ + int sourceLineNumber; + + /** + * Parses a line from the loaded-classes file. + */ + Record(String line, int lineNum) { + char typeChar = line.charAt(0); + switch (typeChar) { + case '>': type = Type.START_LOAD; break; + case '<': type = Type.END_LOAD; break; + case '+': type = Type.START_INIT; break; + case '-': type = Type.END_INIT; break; + default: throw new AssertionError("Bad line: " + line); + } + + sourceLineNumber = lineNum; + + for (int i = 0; i < REPLACE_CLASSES.length; i+= 2) { + line = line.replace(REPLACE_CLASSES[i], REPLACE_CLASSES[i+1]); + } + + line = line.substring(1); + String[] parts = line.split(":"); + + ppid = Integer.parseInt(parts[0]); + pid = Integer.parseInt(parts[1]); + tid = Integer.parseInt(parts[2]); + + processName = decode(parts[3]).intern(); + + classLoader = Integer.parseInt(parts[4]); + className = vmTypeToLanguage(decode(parts[5])).intern(); + + time = Long.parseLong(parts[6]); + } + + /** + * Decode any escaping that may have been written to the log line. + * + * Supports unicode-style escaping: \\uXXXX = character in hex + * + * @param rawField the field as it was written into the log + * @result the same field with any escaped characters replaced + */ + String decode(String rawField) { + String result = rawField; + int offset = result.indexOf("\\u"); + while (offset >= 0) { + String before = result.substring(0, offset); + String escaped = result.substring(offset+2, offset+6); + String after = result.substring(offset+6); + + result = String.format("%s%c%s", before, Integer.parseInt(escaped, 16), after); + + // find another but don't recurse + offset = result.indexOf("\\u", offset + 1); + } + return result; + } + + /** + * Converts a VM-style name to a language-style name. + */ + String vmTypeToLanguage(String typeName) { + // if the typename is (null), just return it as-is. This is probably in dexopt and + // will be discarded anyway. NOTE: This corresponds to the case in dalvik/vm/oo/Class.c + // where dvmLinkClass() returns false and we clean up and exit. + if ("(null)".equals(typeName)) { + return typeName; + } + + if (!typeName.startsWith("L") || !typeName.endsWith(";") ) { + throw new AssertionError("Bad name: " + typeName + " in line " + sourceLineNumber); + } + + typeName = typeName.substring(1, typeName.length() - 1); + return typeName.replace("/", "."); + } +} diff --git a/tools/preload/Root.java b/tools/preload/Root.java new file mode 100644 index 0000000..0bc29bf --- /dev/null +++ b/tools/preload/Root.java @@ -0,0 +1,163 @@ +/* + * 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.io.Serializable; +import java.io.IOException; +import java.io.Writer; +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.io.BufferedInputStream; +import java.io.ObjectOutputStream; +import java.io.BufferedOutputStream; +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.TreeSet; +import java.util.Arrays; +import java.nio.charset.Charset; + +/** + * Root of our data model. + */ +public class Root implements Serializable { + + private static final long serialVersionUID = 0; + + /** pid -> Proc */ + final Map<Integer, Proc> processes = new HashMap<Integer, Proc>(); + + /** Class name -> LoadedClass */ + final Map<String, LoadedClass> loadedClasses + = new HashMap<String, LoadedClass>(); + + MemoryUsage baseline = MemoryUsage.baseline(); + + /** + * Records class loads and initializations. + */ + void indexClassOperation(Record record) { + Proc process = processes.get(record.pid); + + // Ignore dexopt output. It loads applications classes through the + // system class loader and messes us up. + if (record.processName.equals("dexopt")) { + return; + } + + String name = record.className; + LoadedClass loadedClass = loadedClasses.get(name); + Operation o = null; + + switch (record.type) { + case START_LOAD: + case START_INIT: + if (loadedClass == null) { + loadedClass = new LoadedClass( + name, record.classLoader == 0); + if (loadedClass.systemClass) { + // Only measure memory for classes in the boot + // classpath. + loadedClass.measureMemoryUsage(); + } + loadedClasses.put(name, loadedClass); + } + break; + + case END_LOAD: + case END_INIT: + o = process.endOperation(record.tid, record.className, + loadedClass, record.time); + if (o == null) { + return; + } + } + + switch (record.type) { + case START_LOAD: + process.startOperation(record.tid, loadedClass, record.time, + Operation.Type.LOAD); + break; + + case START_INIT: + process.startOperation(record.tid, loadedClass, record.time, + Operation.Type.INIT); + break; + + case END_LOAD: + loadedClass.loads.add(o); + break; + + case END_INIT: + loadedClass.initializations.add(o); + break; + } + } + + /** + * Indexes information about the process from the given record. + */ + void indexProcess(Record record) { + Proc proc = processes.get(record.pid); + + if (proc == null) { + // Create a new process object. + Proc parent = processes.get(record.ppid); + proc = new Proc(parent, record.pid); + processes.put(proc.id, proc); + if (parent != null) { + parent.children.add(proc); + } + } + + proc.setName(record.processName); + } + + /** + * Writes this graph to a file. + */ + void toFile(String fileName) throws IOException { + FileOutputStream out = new FileOutputStream(fileName); + ObjectOutputStream oout = new ObjectOutputStream( + new BufferedOutputStream(out)); + + System.err.println("Writing object model..."); + + oout.writeObject(this); + + oout.close(); + + System.err.println("Done!"); + } + + /** + * Reads Root from a file. + */ + static Root fromFile(String fileName) + throws IOException, ClassNotFoundException { + FileInputStream fin = new FileInputStream(fileName); + ObjectInputStream oin = new ObjectInputStream( + new BufferedInputStream(fin)); + + Root root = (Root) oin.readObject(); + + oin.close(); + + return root; + } +} diff --git a/tools/preload/WritePreloadedClassFile.java b/tools/preload/WritePreloadedClassFile.java new file mode 100644 index 0000000..b067bc2 --- /dev/null +++ b/tools/preload/WritePreloadedClassFile.java @@ -0,0 +1,155 @@ +/* + * 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.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Set; +import java.util.TreeSet; + +/** + * Writes /frameworks/base/preloaded-classes. Also updates + * {@link LoadedClass#preloaded} fields and writes over compiled log file. + */ +public class WritePreloadedClassFile { + + /** + * Preload any class that take longer to load than MIN_LOAD_TIME_MICROS us. + */ + static final int MIN_LOAD_TIME_MICROS = 1250; + + /** + * Preload any class that was loaded by at least MIN_PROCESSES processes. + */ + static final int MIN_PROCESSES = 10; + + public static void main(String[] args) throws IOException, + ClassNotFoundException { + if (args.length != 1) { + System.err.println("Usage: WritePreloadedClassFile [compiled log]"); + System.exit(-1); + } + String rootFile = args[0]; + Root root = Root.fromFile(rootFile); + + // No classes are preloaded to start. + for (LoadedClass loadedClass : root.loadedClasses.values()) { + loadedClass.preloaded = false; + } + + // Open preloaded-classes file for output. + Writer out = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(Policy.PRELOADED_CLASS_FILE), + Charset.forName("US-ASCII"))); + + out.write("# Classes which are preloaded by" + + " com.android.internal.os.ZygoteInit.\n"); + out.write("# Automatically generated by frameworks/base/tools/preload/" + + WritePreloadedClassFile.class.getSimpleName() + ".java.\n"); + out.write("# MIN_LOAD_TIME_MICROS=" + MIN_LOAD_TIME_MICROS + "\n"); + out.write("# MIN_PROCESSES=" + MIN_PROCESSES + "\n"); + + /* + * The set of classes to preload. We preload a class if: + * + * a) it's loaded in the bootclasspath (i.e., is a system class) + * b) it takes > MIN_LOAD_TIME_MICROS us to load, and + * c) it's loaded by more than one process, or it's loaded by an + * application (i.e., not a long running service) + */ + Set<LoadedClass> toPreload = new TreeSet<LoadedClass>(); + + // Preload classes that were loaded by at least 2 processes. Hopefully, + // the memory associated with these classes will be shared. + for (LoadedClass loadedClass : root.loadedClasses.values()) { + Set<String> names = loadedClass.processNames(); + if (!Policy.isPreloadable(loadedClass)) { + continue; + } + + if (names.size() >= MIN_PROCESSES || + (loadedClass.medianTimeMicros() > MIN_LOAD_TIME_MICROS && names.size() > 1)) { + toPreload.add(loadedClass); + } + } + + int initialSize = toPreload.size(); + System.out.println(initialSize + + " classses were loaded by more than one app."); + + // Preload eligable classes from applications (not long-running + // services). + for (Proc proc : root.processes.values()) { + if (proc.fromZygote() && !Policy.isService(proc.name)) { + for (Operation operation : proc.operations) { + LoadedClass loadedClass = operation.loadedClass; + if (shouldPreload(loadedClass)) { + toPreload.add(loadedClass); + } + } + } + } + + System.out.println("Added " + (toPreload.size() - initialSize) + + " more to speed up applications."); + + System.out.println(toPreload.size() + + " total classes will be preloaded."); + + // Make classes that were implicitly loaded by the zygote explicit. + // This adds minimal overhead but avoid confusion about classes not + // appearing in the list. + addAllClassesFrom("zygote", root, toPreload); + + for (LoadedClass loadedClass : toPreload) { + out.write(loadedClass.name + "\n"); + } + + out.close(); + + // Update data to reflect LoadedClass.preloaded changes. + for (LoadedClass loadedClass : toPreload) { + loadedClass.preloaded = true; + } + root.toFile(rootFile); + } + + private static void addAllClassesFrom(String processName, Root root, + Set<LoadedClass> toPreload) { + for (Proc proc : root.processes.values()) { + if (proc.name.equals(processName)) { + for (Operation operation : proc.operations) { + boolean preloadable + = Policy.isPreloadable(operation.loadedClass); + if (preloadable) { + toPreload.add(operation.loadedClass); + } + } + } + } + } + + /** + * Returns true if the class should be preloaded. + */ + private static boolean shouldPreload(LoadedClass clazz) { + return Policy.isPreloadable(clazz) + && clazz.medianTimeMicros() > MIN_LOAD_TIME_MICROS; + } +} diff --git a/tools/preload/loadclass/Android.mk b/tools/preload/loadclass/Android.mk new file mode 100644 index 0000000..65828be --- /dev/null +++ b/tools/preload/loadclass/Android.mk @@ -0,0 +1,9 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_MODULE_TAGS := tests + +LOCAL_MODULE := loadclass + +include $(BUILD_JAVA_LIBRARY) diff --git a/tools/preload/loadclass/LoadClass.java b/tools/preload/loadclass/LoadClass.java new file mode 100644 index 0000000..a71b6a8 --- /dev/null +++ b/tools/preload/loadclass/LoadClass.java @@ -0,0 +1,84 @@ +/* + * 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 android.util.Log; +import android.os.Debug; + +/** + * Loads a class, runs the garbage collector, and prints showmap output. + * + * <p>Usage: dalvikvm LoadClass [class name] + */ +class LoadClass { + + public static void main(String[] args) { + System.loadLibrary("android_runtime"); + + if (registerNatives() < 0) { + throw new RuntimeException("Error registering natives."); + } + + Debug.startAllocCounting(); + + if (args.length > 0) { + try { + long start = System.currentTimeMillis(); + Class.forName(args[0]); + long elapsed = System.currentTimeMillis() - start; + Log.i("LoadClass", "Loaded " + args[0] + " in " + elapsed + + "ms."); + } catch (ClassNotFoundException e) { + Log.w("LoadClass", e); + return; + } + } + + System.gc(); + + int allocCount = Debug.getGlobalAllocCount(); + int allocSize = Debug.getGlobalAllocSize(); + int freedCount = Debug.getGlobalFreedCount(); + int freedSize = Debug.getGlobalFreedSize(); + long nativeHeapSize = Debug.getNativeHeapSize(); + + Debug.stopAllocCounting(); + + StringBuilder response = new StringBuilder("DECAFBAD"); + + int[] pages = new int[6]; + Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo(); + Debug.getMemoryInfo(memoryInfo); + response.append(',').append(memoryInfo.nativeSharedDirty); + response.append(',').append(memoryInfo.dalvikSharedDirty); + response.append(',').append(memoryInfo.otherSharedDirty); + response.append(',').append(memoryInfo.nativePrivateDirty); + response.append(',').append(memoryInfo.dalvikPrivateDirty); + response.append(',').append(memoryInfo.otherPrivateDirty); + + response.append(',').append(allocCount); + response.append(',').append(allocSize); + response.append(',').append(freedCount); + response.append(',').append(freedSize); + response.append(',').append(nativeHeapSize); + + System.out.println(response.toString()); + } + + /** + * Registers native functions. See AndroidRuntime.cpp. + */ + static native int registerNatives(); +} diff --git a/tools/preload/preload.iml b/tools/preload/preload.iml new file mode 100644 index 0000000..2d87c55 --- /dev/null +++ b/tools/preload/preload.iml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module relativePaths="true" type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file:///tmp/preload" /> + <output-test url="file:///tmp/preload" /> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> + diff --git a/tools/preload/preload.ipr b/tools/preload/preload.ipr new file mode 100644 index 0000000..0c9621c --- /dev/null +++ b/tools/preload/preload.ipr @@ -0,0 +1,495 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project relativePaths="false" version="4"> + <component name="AntConfiguration"> + <defaultAnt bundledAnt="true" /> + </component> + <component name="BuildJarProjectSettings"> + <option name="BUILD_JARS_ON_MAKE" value="false" /> + </component> + <component name="ChangeBrowserSettings"> + <option name="MAIN_SPLITTER_PROPORTION" value="0.3" /> + <option name="MESSAGES_SPLITTER_PROPORTION" value="0.8" /> + <option name="USE_DATE_BEFORE_FILTER" value="false" /> + <option name="USE_DATE_AFTER_FILTER" value="false" /> + <option name="USE_CHANGE_BEFORE_FILTER" value="false" /> + <option name="USE_CHANGE_AFTER_FILTER" value="false" /> + <option name="DATE_BEFORE" value="" /> + <option name="DATE_AFTER" value="" /> + <option name="CHANGE_BEFORE" value="" /> + <option name="CHANGE_AFTER" value="" /> + <option name="USE_USER_FILTER" value="false" /> + <option name="USER" value="" /> + </component> + <component name="CodeStyleProjectProfileManger"> + <option name="PROJECT_PROFILE" /> + <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" /> + </component> + <component name="CodeStyleSettingsManager"> + <option name="PER_PROJECT_SETTINGS"> + <value> + <ADDITIONAL_INDENT_OPTIONS fileType="java"> + <option name="INDENT_SIZE" value="4" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + <ADDITIONAL_INDENT_OPTIONS fileType="xml"> + <option name="INDENT_SIZE" value="4" /> + <option name="CONTINUATION_INDENT_SIZE" value="8" /> + <option name="TAB_SIZE" value="4" /> + <option name="USE_TAB_CHARACTER" value="false" /> + <option name="SMART_TABS" value="false" /> + <option name="LABEL_INDENT_SIZE" value="0" /> + <option name="LABEL_INDENT_ABSOLUTE" value="false" /> + </ADDITIONAL_INDENT_OPTIONS> + </value> + </option> + <option name="USE_PER_PROJECT_SETTINGS" value="false" /> + </component> + <component name="CompilerConfiguration"> + <option name="DEFAULT_COMPILER" value="Javac" /> + <option name="DEPLOY_AFTER_MAKE" value="0" /> + <resourceExtensions> + <entry name=".+\.(properties|xml|html|dtd|tld)" /> + <entry name=".+\.(gif|png|jpeg|jpg)" /> + </resourceExtensions> + <wildcardResourcePatterns> + <entry name="?*.properties" /> + <entry name="?*.xml" /> + <entry name="?*.gif" /> + <entry name="?*.png" /> + <entry name="?*.jpeg" /> + <entry name="?*.jpg" /> + <entry name="?*.html" /> + <entry name="?*.dtd" /> + <entry name="?*.tld" /> + </wildcardResourcePatterns> + </component> + <component name="Cvs2Configuration"> + <option name="PRUNE_EMPTY_DIRECTORIES" value="true" /> + <option name="MERGING_MODE" value="0" /> + <option name="MERGE_WITH_BRANCH1_NAME" value="HEAD" /> + <option name="MERGE_WITH_BRANCH2_NAME" value="HEAD" /> + <option name="RESET_STICKY" value="false" /> + <option name="CREATE_NEW_DIRECTORIES" value="true" /> + <option name="DEFAULT_TEXT_FILE_SUBSTITUTION" value="kv" /> + <option name="PROCESS_UNKNOWN_FILES" value="false" /> + <option name="PROCESS_DELETED_FILES" value="false" /> + <option name="PROCESS_IGNORED_FILES" value="false" /> + <option name="RESERVED_EDIT" value="false" /> + <option name="CHECKOUT_DATE_OR_REVISION_SETTINGS"> + <value> + <option name="BRANCH" value="" /> + <option name="DATE" value="" /> + <option name="USE_BRANCH" value="false" /> + <option name="USE_DATE" value="false" /> + </value> + </option> + <option name="UPDATE_DATE_OR_REVISION_SETTINGS"> + <value> + <option name="BRANCH" value="" /> + <option name="DATE" value="" /> + <option name="USE_BRANCH" value="false" /> + <option name="USE_DATE" value="false" /> + </value> + </option> + <option name="SHOW_CHANGES_REVISION_SETTINGS"> + <value> + <option name="BRANCH" value="" /> + <option name="DATE" value="" /> + <option name="USE_BRANCH" value="false" /> + <option name="USE_DATE" value="false" /> + </value> + </option> + <option name="SHOW_OUTPUT" value="false" /> + <option name="ADD_WATCH_INDEX" value="0" /> + <option name="REMOVE_WATCH_INDEX" value="0" /> + <option name="UPDATE_KEYWORD_SUBSTITUTION" /> + <option name="MAKE_NEW_FILES_READONLY" value="false" /> + <option name="SHOW_CORRUPTED_PROJECT_FILES" value="0" /> + <option name="TAG_AFTER_PROJECT_COMMIT" value="false" /> + <option name="OVERRIDE_EXISTING_TAG_FOR_PROJECT" value="true" /> + <option name="TAG_AFTER_PROJECT_COMMIT_NAME" value="" /> + <option name="CLEAN_COPY" value="false" /> + </component> + <component name="DependenciesAnalyzeManager"> + <option name="myForwardDirection" value="false" /> + </component> + <component name="DependencyValidationManager"> + <option name="SKIP_IMPORT_STATEMENTS" value="false" /> + </component> + <component name="EclipseCompilerSettings"> + <option name="DEBUGGING_INFO" value="true" /> + <option name="GENERATE_NO_WARNINGS" value="true" /> + <option name="DEPRECATION" value="false" /> + <option name="ADDITIONAL_OPTIONS_STRING" value="" /> + <option name="MAXIMUM_HEAP_SIZE" value="128" /> + </component> + <component name="EclipseEmbeddedCompilerSettings"> + <option name="DEBUGGING_INFO" value="true" /> + <option name="GENERATE_NO_WARNINGS" value="true" /> + <option name="DEPRECATION" value="false" /> + <option name="ADDITIONAL_OPTIONS_STRING" value="" /> + <option name="MAXIMUM_HEAP_SIZE" value="128" /> + </component> + <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" /> + <component name="EntryPointsManager"> + <entry_points version="2.0" /> + </component> + <component name="ExportToHTMLSettings"> + <option name="PRINT_LINE_NUMBERS" value="false" /> + <option name="OPEN_IN_BROWSER" value="false" /> + <option name="OUTPUT_DIRECTORY" /> + </component> + <component name="IdProvider" IDEtalkID="D171F99B9178C1675593DC9A76A5CC7E" /> + <component name="InspectionProjectProfileManager"> + <option name="PROJECT_PROFILE" value="Project Default" /> + <option name="USE_PROJECT_PROFILE" value="true" /> + <version value="1.0" /> + <profiles> + <profile version="1.0" is_locked="false"> + <option name="myName" value="Project Default" /> + <option name="myLocal" value="false" /> + <inspection_tool class="JavaDoc" enabled="false" level="WARNING" enabled_by_default="false"> + <option name="TOP_LEVEL_CLASS_OPTIONS"> + <value> + <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> + <option name="REQUIRED_TAGS" value="" /> + </value> + </option> + <option name="INNER_CLASS_OPTIONS"> + <value> + <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> + <option name="REQUIRED_TAGS" value="" /> + </value> + </option> + <option name="METHOD_OPTIONS"> + <value> + <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> + <option name="REQUIRED_TAGS" value="@return@param@throws or @exception" /> + </value> + </option> + <option name="FIELD_OPTIONS"> + <value> + <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" /> + <option name="REQUIRED_TAGS" value="" /> + </value> + </option> + <option name="IGNORE_DEPRECATED" value="false" /> + <option name="IGNORE_JAVADOC_PERIOD" value="true" /> + <option name="myAdditionalJavadocTags" value="" /> + </inspection_tool> + <inspection_tool class="JavaLangImport" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="OnDemandImport" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="RedundantImport" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="SamePackageImport" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="UnusedImport" enabled="true" level="WARNING" enabled_by_default="true" /> + </profile> + </profiles> + <list size="4"> + <item index="0" class="java.lang.String" itemvalue="WARNING" /> + <item index="1" class="java.lang.String" itemvalue="SERVER PROBLEM" /> + <item index="2" class="java.lang.String" itemvalue="INFO" /> + <item index="3" class="java.lang.String" itemvalue="ERROR" /> + </list> + </component> + <component name="JavacSettings"> + <option name="DEBUGGING_INFO" value="true" /> + <option name="GENERATE_NO_WARNINGS" value="false" /> + <option name="DEPRECATION" value="true" /> + <option name="ADDITIONAL_OPTIONS_STRING" value="" /> + <option name="MAXIMUM_HEAP_SIZE" value="128" /> + </component> + <component name="JavadocGenerationManager"> + <option name="OUTPUT_DIRECTORY" /> + <option name="OPTION_SCOPE" value="protected" /> + <option name="OPTION_HIERARCHY" value="true" /> + <option name="OPTION_NAVIGATOR" value="true" /> + <option name="OPTION_INDEX" value="true" /> + <option name="OPTION_SEPARATE_INDEX" value="true" /> + <option name="OPTION_DOCUMENT_TAG_USE" value="false" /> + <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" /> + <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" /> + <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" /> + <option name="OPTION_DEPRECATED_LIST" value="true" /> + <option name="OTHER_OPTIONS" value="" /> + <option name="HEAP_SIZE" /> + <option name="LOCALE" /> + <option name="OPEN_IN_BROWSER" value="true" /> + </component> + <component name="JikesSettings"> + <option name="JIKES_PATH" value="" /> + <option name="DEBUGGING_INFO" value="true" /> + <option name="DEPRECATION" value="true" /> + <option name="GENERATE_NO_WARNINGS" value="false" /> + <option name="IS_EMACS_ERRORS_MODE" value="true" /> + <option name="ADDITIONAL_OPTIONS_STRING" value="" /> + </component> + <component name="LogConsolePreferences"> + <option name="FILTER_ERRORS" value="false" /> + <option name="FILTER_WARNINGS" value="false" /> + <option name="FILTER_INFO" value="true" /> + <option name="CUSTOM_FILTER" /> + </component> + <component name="Palette2"> + <group name="Swing"> + <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> + </item> + <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true"> + <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> + <initial-values> + <property name="text" value="Button" /> + </initial-values> + </item> + <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="RadioButton" /> + </initial-values> + </item> + <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="CheckBox" /> + </initial-values> + </item> + <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="Label" /> + </initial-values> + </item> + <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1"> + <preferred-size width="-1" height="20" /> + </default-constraints> + </item> + <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" /> + </item> + </group> + </component> + <component name="PerforceChangeBrowserSettings"> + <option name="USE_CLIENT_FILTER" value="true" /> + <option name="CLIENT" value="" /> + </component> + <component name="ProjectDetails"> + <option name="projectName" value="preload" /> + </component> + <component name="ProjectFileVersion" converted="true" /> + <component name="ProjectKey"> + <option name="state" value="project:///Volumes/Android/donut/frameworks/base/tools/preload/preload.ipr" /> + </component> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/preload.iml" filepath="$PROJECT_DIR$/preload.iml" /> + </modules> + </component> + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-name="1.5" project-jdk-type="JavaSDK"> + <output url="file:///tmp/preload" /> + </component> + <component name="RmicSettings"> + <option name="IS_EANABLED" value="false" /> + <option name="DEBUGGING_INFO" value="true" /> + <option name="GENERATE_NO_WARNINGS" value="false" /> + <option name="GENERATE_IIOP_STUBS" value="false" /> + <option name="ADDITIONAL_OPTIONS_STRING" value="" /> + </component> + <component name="StarteamConfiguration"> + <option name="SERVER" value="" /> + <option name="PORT" value="49201" /> + <option name="USER" value="" /> + <option name="PASSWORD" value="" /> + <option name="PROJECT" value="" /> + <option name="VIEW" value="" /> + <option name="ALTERNATIVE_WORKING_PATH" value="" /> + <option name="LOCK_ON_CHECKOUT" value="false" /> + <option name="UNLOCK_ON_CHECKIN" value="false" /> + </component> + <component name="Struts Assistant"> + <option name="showInputs" value="true" /> + <option name="resources"> + <value> + <option name="strutsPath" /> + <option name="strutsHelp" /> + </value> + </option> + <option name="selectedTaglibs" /> + <option name="selectedTaglibs" /> + <option name="myStrutsValidationEnabled" value="true" /> + <option name="myTilesValidationEnabled" value="true" /> + <option name="myValidatorValidationEnabled" value="true" /> + <option name="myReportErrorsAsWarnings" value="true" /> + </component> + <component name="SvnBranchConfigurationManager"> + <option name="mySupportsUserInfoFilter" value="true" /> + </component> + <component name="SvnChangesBrowserSettings"> + <option name="USE_AUTHOR_FIELD" value="true" /> + <option name="AUTHOR" value="" /> + <option name="LOCATION" value="" /> + <option name="USE_PROJECT_SETTINGS" value="true" /> + <option name="USE_ALTERNATE_LOCATION" value="false" /> + </component> + <component name="VCS.FileViewConfiguration"> + <option name="SELECTED_STATUSES" value="DEFAULT" /> + <option name="SELECTED_COLUMNS" value="DEFAULT" /> + <option name="SHOW_FILTERS" value="true" /> + <option name="CUSTOMIZE_VIEW" value="true" /> + <option name="SHOW_FILE_HISTORY_AS_TREE" value="true" /> + </component> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Perforce" /> + <mapping directory="/Volumes/Android/donut/frameworks/base" vcs="Git" /> + </component> + <component name="VssConfiguration"> + <option name="CLIENT_PATH" value="" /> + <option name="SRCSAFEINI_PATH" value="" /> + <option name="USER_NAME" value="" /> + <option name="PWD" value="" /> + <option name="VSS_IS_INITIALIZED" value="false" /> + <CheckoutOptions> + <option name="COMMENT" value="" /> + <option name="DO_NOT_GET_LATEST_VERSION" value="false" /> + <option name="REPLACE_WRITABLE" value="false" /> + <option name="RECURSIVE" value="false" /> + </CheckoutOptions> + <CheckinOptions> + <option name="COMMENT" value="" /> + <option name="KEEP_CHECKED_OUT" value="false" /> + <option name="RECURSIVE" value="false" /> + </CheckinOptions> + <AddOptions> + <option name="STORE_ONLY_LATEST_VERSION" value="false" /> + <option name="CHECK_OUT_IMMEDIATELY" value="false" /> + <option name="FILE_TYPE" value="0" /> + </AddOptions> + <UndocheckoutOptions> + <option name="MAKE_WRITABLE" value="false" /> + <option name="REPLACE_LOCAL_COPY" value="0" /> + <option name="RECURSIVE" value="false" /> + </UndocheckoutOptions> + <GetOptions> + <option name="REPLACE_WRITABLE" value="0" /> + <option name="MAKE_WRITABLE" value="false" /> + <option name="ANSWER_NEGATIVELY" value="false" /> + <option name="ANSWER_POSITIVELY" value="false" /> + <option name="RECURSIVE" value="false" /> + <option name="VERSION" /> + </GetOptions> + <VssConfigurableExcludedFilesTag /> + </component> + <component name="antWorkspaceConfiguration"> + <option name="IS_AUTOSCROLL_TO_SOURCE" value="false" /> + <option name="FILTER_TARGETS" value="false" /> + </component> + <component name="com.intellij.ide.util.scopeChooser.ScopeChooserConfigurable" proportions="" version="1"> + <option name="myLastEditedConfigurable" /> + </component> + <component name="com.intellij.jsf.UserDefinedFacesConfigs"> + <option name="USER_DEFINED_CONFIGS"> + <value> + <list size="0" /> + </value> + </option> + </component> + <component name="com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectRootMasterDetailsConfigurable" proportions="" version="1"> + <option name="myPlainMode" value="false" /> + <option name="myLastEditedConfigurable" /> + </component> + <component name="com.intellij.profile.ui.ErrorOptionsConfigurable" proportions="" version="1"> + <option name="myLastEditedConfigurable" /> + </component> + <component name="uidesigner-configuration"> + <option name="INSTRUMENT_CLASSES" value="true" /> + <option name="COPY_FORMS_RUNTIME_TO_OUTPUT" value="true" /> + <option name="DEFAULT_LAYOUT_MANAGER" value="GridLayoutManager" /> + </component> +</project> + diff --git a/tools/preload/sorttable.js b/tools/preload/sorttable.js new file mode 100644 index 0000000..25bccb2 --- /dev/null +++ b/tools/preload/sorttable.js @@ -0,0 +1,493 @@ +/* + SortTable + version 2 + 7th April 2007 + Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/ + + Instructions: + Download this file + Add <script src="sorttable.js"></script> to your HTML + Add class="sortable" to any table you'd like to make sortable + Click on the headers to sort + + Thanks to many, many people for contributions and suggestions. + Licenced as X11: http://www.kryogenix.org/code/browser/licence.html + This basically means: do what you want with it. +*/ + + +var stIsIE = /*@cc_on!@*/false; + +sorttable = { + init: function() { + // quit if this function has already been called + if (arguments.callee.done) return; + // flag this function so we don't do the same thing twice + arguments.callee.done = true; + // kill the timer + if (_timer) clearInterval(_timer); + + if (!document.createElement || !document.getElementsByTagName) return; + + sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; + + forEach(document.getElementsByTagName('table'), function(table) { + if (table.className.search(/\bsortable\b/) != -1) { + sorttable.makeSortable(table); + } + }); + + }, + + makeSortable: function(table) { + if (table.getElementsByTagName('thead').length == 0) { + // table doesn't have a tHead. Since it should have, create one and + // put the first table row in it. + the = document.createElement('thead'); + the.appendChild(table.rows[0]); + table.insertBefore(the,table.firstChild); + } + // Safari doesn't support table.tHead, sigh + if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0]; + + if (table.tHead.rows.length != 1) return; // can't cope with two header rows + + // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as + // "total" rows, for example). This is B&R, since what you're supposed + // to do is put them in a tfoot. So, if there are sortbottom rows, + // for backwards compatibility, move them to tfoot (creating it if needed). + sortbottomrows = []; + for (var i=0; i<table.rows.length; i++) { + if (table.rows[i].className.search(/\bsortbottom\b/) != -1) { + sortbottomrows[sortbottomrows.length] = table.rows[i]; + } + } + if (sortbottomrows) { + if (table.tFoot == null) { + // table doesn't have a tfoot. Create one. + tfo = document.createElement('tfoot'); + table.appendChild(tfo); + } + for (var i=0; i<sortbottomrows.length; i++) { + tfo.appendChild(sortbottomrows[i]); + } + delete sortbottomrows; + } + + // work through each column and calculate its type + headrow = table.tHead.rows[0].cells; + for (var i=0; i<headrow.length; i++) { + // manually override the type with a sorttable_type attribute + if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col + mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/); + if (mtch) { override = mtch[1]; } + if (mtch && typeof sorttable["sort_"+override] == 'function') { + headrow[i].sorttable_sortfunction = sorttable["sort_"+override]; + } else { + headrow[i].sorttable_sortfunction = sorttable.guessType(table,i); + } + // make it clickable to sort + headrow[i].sorttable_columnindex = i; + headrow[i].sorttable_tbody = table.tBodies[0]; + dean_addEvent(headrow[i],"click", function(e) { + + if (this.className.search(/\bsorttable_sorted\b/) != -1) { + // if we're already sorted by this column, just + // reverse the table, which is quicker + sorttable.reverse(this.sorttable_tbody); + this.className = this.className.replace('sorttable_sorted', + 'sorttable_sorted_reverse'); + this.removeChild(document.getElementById('sorttable_sortfwdind')); + sortrevind = document.createElement('span'); + sortrevind.id = "sorttable_sortrevind"; + sortrevind.innerHTML = stIsIE ? ' <font face="webdings">5</font>' : ' ▴'; + this.appendChild(sortrevind); + return; + } + if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) { + // if we're already sorted by this column in reverse, just + // re-reverse the table, which is quicker + sorttable.reverse(this.sorttable_tbody); + this.className = this.className.replace('sorttable_sorted_reverse', + 'sorttable_sorted'); + this.removeChild(document.getElementById('sorttable_sortrevind')); + sortfwdind = document.createElement('span'); + sortfwdind.id = "sorttable_sortfwdind"; + sortfwdind.innerHTML = stIsIE ? ' <font face="webdings">6</font>' : ' ▾'; + this.appendChild(sortfwdind); + return; + } + + // remove sorttable_sorted classes + theadrow = this.parentNode; + forEach(theadrow.childNodes, function(cell) { + if (cell.nodeType == 1) { // an element + cell.className = cell.className.replace('sorttable_sorted_reverse',''); + cell.className = cell.className.replace('sorttable_sorted',''); + } + }); + sortfwdind = document.getElementById('sorttable_sortfwdind'); + if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); } + sortrevind = document.getElementById('sorttable_sortrevind'); + if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); } + + this.className += ' sorttable_sorted'; + sortfwdind = document.createElement('span'); + sortfwdind.id = "sorttable_sortfwdind"; + sortfwdind.innerHTML = stIsIE ? ' <font face="webdings">6</font>' : ' ▾'; + this.appendChild(sortfwdind); + + // build an array to sort. This is a Schwartzian transform thing, + // i.e., we "decorate" each row with the actual sort key, + // sort based on the sort keys, and then put the rows back in order + // which is a lot faster because you only do getInnerText once per row + row_array = []; + col = this.sorttable_columnindex; + rows = this.sorttable_tbody.rows; + for (var j=0; j<rows.length; j++) { + row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]]; + } + /* If you want a stable sort, uncomment the following line */ + //sorttable.shaker_sort(row_array, this.sorttable_sortfunction); + /* and comment out this one */ + row_array.sort(this.sorttable_sortfunction); + + tb = this.sorttable_tbody; + for (var j=0; j<row_array.length; j++) { + tb.appendChild(row_array[j][1]); + } + + delete row_array; + }); + } + } + }, + + guessType: function(table, column) { + // guess the type of a column based on its first non-blank row + sortfn = sorttable.sort_alpha; + for (var i=0; i<table.tBodies[0].rows.length; i++) { + text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]); + if (text != '') { + if (text.match(/^-?[£$¤]?[\d,.]+%?$/)) { + return sorttable.sort_numeric; + } + // check for a date: dd/mm/yyyy or dd/mm/yy + // can have / or . or - as separator + // can be mm/dd as well + possdate = text.match(sorttable.DATE_RE) + if (possdate) { + // looks like a date + first = parseInt(possdate[1]); + second = parseInt(possdate[2]); + if (first > 12) { + // definitely dd/mm + return sorttable.sort_ddmm; + } else if (second > 12) { + return sorttable.sort_mmdd; + } else { + // looks like a date, but we can't tell which, so assume + // that it's dd/mm (English imperialism!) and keep looking + sortfn = sorttable.sort_ddmm; + } + } + } + } + return sortfn; + }, + + getInnerText: function(node) { + // gets the text we want to use for sorting for a cell. + // strips leading and trailing whitespace. + // this is *not* a generic getInnerText function; it's special to sorttable. + // for example, you can override the cell text with a customkey attribute. + // it also gets .value for <input> fields. + + hasInputs = (typeof node.getElementsByTagName == 'function') && + node.getElementsByTagName('input').length; + + if (node.getAttribute("sorttable_customkey") != null) { + return node.getAttribute("sorttable_customkey"); + } + else if (typeof node.textContent != 'undefined' && !hasInputs) { + return node.textContent.replace(/^\s+|\s+$/g, ''); + } + else if (typeof node.innerText != 'undefined' && !hasInputs) { + return node.innerText.replace(/^\s+|\s+$/g, ''); + } + else if (typeof node.text != 'undefined' && !hasInputs) { + return node.text.replace(/^\s+|\s+$/g, ''); + } + else { + switch (node.nodeType) { + case 3: + if (node.nodeName.toLowerCase() == 'input') { + return node.value.replace(/^\s+|\s+$/g, ''); + } + case 4: + return node.nodeValue.replace(/^\s+|\s+$/g, ''); + break; + case 1: + case 11: + var innerText = ''; + for (var i = 0; i < node.childNodes.length; i++) { + innerText += sorttable.getInnerText(node.childNodes[i]); + } + return innerText.replace(/^\s+|\s+$/g, ''); + break; + default: + return ''; + } + } + }, + + reverse: function(tbody) { + // reverse the rows in a tbody + newrows = []; + for (var i=0; i<tbody.rows.length; i++) { + newrows[newrows.length] = tbody.rows[i]; + } + for (var i=newrows.length-1; i>=0; i--) { + tbody.appendChild(newrows[i]); + } + delete newrows; + }, + + /* sort functions + each sort function takes two parameters, a and b + you are comparing a[0] and b[0] */ + sort_numeric: function(a,b) { + aa = parseFloat(a[0].replace(/[^0-9.-]/g,'')); + if (isNaN(aa)) aa = 0; + bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); + if (isNaN(bb)) bb = 0; + return aa-bb; + }, + sort_alpha: function(a,b) { + if (a[0]==b[0]) return 0; + if (a[0]<b[0]) return -1; + return 1; + }, + sort_ddmm: function(a,b) { + mtch = a[0].match(sorttable.DATE_RE); + y = mtch[3]; m = mtch[2]; d = mtch[1]; + if (m.length == 1) m = '0'+m; + if (d.length == 1) d = '0'+d; + dt1 = y+m+d; + mtch = b[0].match(sorttable.DATE_RE); + y = mtch[3]; m = mtch[2]; d = mtch[1]; + if (m.length == 1) m = '0'+m; + if (d.length == 1) d = '0'+d; + dt2 = y+m+d; + if (dt1==dt2) return 0; + if (dt1<dt2) return -1; + return 1; + }, + sort_mmdd: function(a,b) { + mtch = a[0].match(sorttable.DATE_RE); + y = mtch[3]; d = mtch[2]; m = mtch[1]; + if (m.length == 1) m = '0'+m; + if (d.length == 1) d = '0'+d; + dt1 = y+m+d; + mtch = b[0].match(sorttable.DATE_RE); + y = mtch[3]; d = mtch[2]; m = mtch[1]; + if (m.length == 1) m = '0'+m; + if (d.length == 1) d = '0'+d; + dt2 = y+m+d; + if (dt1==dt2) return 0; + if (dt1<dt2) return -1; + return 1; + }, + + shaker_sort: function(list, comp_func) { + // A stable sort function to allow multi-level sorting of data + // see: http://en.wikipedia.org/wiki/Cocktail_sort + // thanks to Joseph Nahmias + var b = 0; + var t = list.length - 1; + var swap = true; + + while(swap) { + swap = false; + for(var i = b; i < t; ++i) { + if ( comp_func(list[i], list[i+1]) > 0 ) { + var q = list[i]; list[i] = list[i+1]; list[i+1] = q; + swap = true; + } + } // for + t--; + + if (!swap) break; + + for(var i = t; i > b; --i) { + if ( comp_func(list[i], list[i-1]) < 0 ) { + var q = list[i]; list[i] = list[i-1]; list[i-1] = q; + swap = true; + } + } // for + b++; + + } // while(swap) + } +} + +/* ****************************************************************** + Supporting functions: bundled here to avoid depending on a library + ****************************************************************** */ + +// Dean Edwards/Matthias Miller/John Resig + +/* for Mozilla/Opera9 */ +if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", sorttable.init, false); +} + +/* for Internet Explorer */ +/*@cc_on @*/ +/*@if (@_win32) + document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>"); + var script = document.getElementById("__ie_onload"); + script.onreadystatechange = function() { + if (this.readyState == "complete") { + sorttable.init(); // call the onload handler + } + }; +/*@end @*/ + +/* for Safari */ +if (/WebKit/i.test(navigator.userAgent)) { // sniff + var _timer = setInterval(function() { + if (/loaded|complete/.test(document.readyState)) { + sorttable.init(); // call the onload handler + } + }, 10); +} + +/* for other browsers */ +window.onload = sorttable.init; + +// written by Dean Edwards, 2005 +// with input from Tino Zijdel, Matthias Miller, Diego Perini + +// http://dean.edwards.name/weblog/2005/10/add-event/ + +function dean_addEvent(element, type, handler) { + if (element.addEventListener) { + element.addEventListener(type, handler, false); + } else { + // assign each event handler a unique ID + if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++; + // create a hash table of event types for the element + if (!element.events) element.events = {}; + // create a hash table of event handlers for each element/event pair + var handlers = element.events[type]; + if (!handlers) { + handlers = element.events[type] = {}; + // store the existing event handler (if there is one) + if (element["on" + type]) { + handlers[0] = element["on" + type]; + } + } + // store the event handler in the hash table + handlers[handler.$$guid] = handler; + // assign a global event handler to do all the work + element["on" + type] = handleEvent; + } +}; +// a counter used to create unique IDs +dean_addEvent.guid = 1; + +function removeEvent(element, type, handler) { + if (element.removeEventListener) { + element.removeEventListener(type, handler, false); + } else { + // delete the event handler from the hash table + if (element.events && element.events[type]) { + delete element.events[type][handler.$$guid]; + } + } +}; + +function handleEvent(event) { + var returnValue = true; + // grab the event object (IE uses a global event object) + event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event); + // get a reference to the hash table of event handlers + var handlers = this.events[event.type]; + // execute each event handler + for (var i in handlers) { + this.$$handleEvent = handlers[i]; + if (this.$$handleEvent(event) === false) { + returnValue = false; + } + } + return returnValue; +}; + +function fixEvent(event) { + // add W3C standard event methods + event.preventDefault = fixEvent.preventDefault; + event.stopPropagation = fixEvent.stopPropagation; + return event; +}; +fixEvent.preventDefault = function() { + this.returnValue = false; +}; +fixEvent.stopPropagation = function() { + this.cancelBubble = true; +} + +// Dean's forEach: http://dean.edwards.name/base/forEach.js +/* + forEach, version 1.0 + Copyright 2006, Dean Edwards + License: http://www.opensource.org/licenses/mit-license.php +*/ + +// array-like enumeration +if (!Array.forEach) { // mozilla already supports this + Array.forEach = function(array, block, context) { + for (var i = 0; i < array.length; i++) { + block.call(context, array[i], i, array); + } + }; +} + +// generic enumeration +Function.prototype.forEach = function(object, block, context) { + for (var key in object) { + if (typeof this.prototype[key] == "undefined") { + block.call(context, object[key], key, object); + } + } +}; + +// character enumeration +String.forEach = function(string, block, context) { + Array.forEach(string.split(""), function(chr, index) { + block.call(context, chr, index, string); + }); +}; + +// globally resolve forEach enumeration +var forEach = function(object, block, context) { + if (object) { + var resolve = Object; // default + if (object instanceof Function) { + // functions have a "length" property + resolve = Function; + } else if (object.forEach instanceof Function) { + // the object implements a custom forEach method so use that + object.forEach(block, context); + return; + } else if (typeof object == "string") { + // the object is a string + resolve = String; + } else if (typeof object.length == "number") { + // the object is array-like + resolve = Array; + } + resolve.forEach(object, block, context); + } +}; + |