diff options
| author | 2016-01-08 17:44:54 +0000 | |
|---|---|---|
| committer | 2016-01-08 17:44:54 +0000 | |
| commit | f2eb8b236fada3e7223617689776ef44c9403cfa (patch) | |
| tree | 3292ab2615dfa5eb39fb3a0f68d9d7db5c22061d | |
| parent | 7e308606f605dd1bd047e20e17ee53122a64b8ba (diff) | |
| parent | f2c7983de141d008c7c02e3331fbde67e13ac46c (diff) | |
Merge "Frameworks/base: New preload tool"
am: f2c7983de1
* commit 'f2c7983de141d008c7c02e3331fbde67e13ac46c':
Frameworks/base: New preload tool
28 files changed, 3056 insertions, 0 deletions
diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk new file mode 100644 index 000000000000..35d28fbfeba8 --- /dev/null +++ b/tools/preload2/Android.mk @@ -0,0 +1,32 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk + +# To connect to devices (and take hprof dumps). +LOCAL_STATIC_JAVA_LIBRARIES := ddmlib-prebuilt + +# To process hprof dumps. +LOCAL_STATIC_JAVA_LIBRARIES += perflib-prebuilt trove-prebuilt guavalib + +# For JDWP access we use the framework in the JDWP tests from Apache Harmony, for +# convenience (and to not depend on internal JDK APIs). +LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit + +LOCAL_MODULE:= preload2 + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Copy the preload-tool shell script to the host's bin directory. +include $(CLEAR_VARS) +LOCAL_IS_HOST_MODULE := true +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE := preload-tool +include $(BUILD_SYSTEM)/base_rules.mk +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/preload-tool $(ACP) + @echo "Copy: $(PRIVATE_MODULE) ($@)" + $(copy-file-to-new-target) + $(hide) chmod 755 $@ diff --git a/tools/preload2/preload-tool b/tools/preload2/preload-tool new file mode 100644 index 000000000000..36dbc1c775b1 --- /dev/null +++ b/tools/preload2/preload-tool @@ -0,0 +1,37 @@ +# Copyright (C) 2015 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. + +# This script is used on the host only. It uses a common subset +# shell dialect that should work well. It is partially derived +# from art/tools/art. + +function follow_links() { + if [ z"$BASH_SOURCE" != z ]; then + file="$BASH_SOURCE" + else + file="$0" + fi + while [ -h "$file" ]; do + # On Mac OS, readlink -f doesn't work. + file="$(readlink "$file")" + done + echo "$file" +} + + +PROG_NAME="$(follow_links)" +PROG_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)" +ANDROID_ROOT=$PROG_DIR/.. + +java -cp $ANDROID_ROOT/framework/preload2.jar com.android.preload.Main diff --git a/tools/preload2/src/com/android/preload/ClientUtils.java b/tools/preload2/src/com/android/preload/ClientUtils.java new file mode 100644 index 000000000000..71ef025d6587 --- /dev/null +++ b/tools/preload2/src/com/android/preload/ClientUtils.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload; + +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; + +/** + * Helper class for common communication with a Client (the ddms name for a running application). + * + * Instances take a default timeout parameter that's applied to all functions without explicit + * timeout. Timeouts are in milliseconds. + */ +public class ClientUtils { + + private int defaultTimeout; + + public ClientUtils() { + this(10000); + } + + public ClientUtils(int defaultTimeout) { + this.defaultTimeout = defaultTimeout; + } + + /** + * Shortcut for findClient with default timeout. + */ + public Client findClient(IDevice device, String processName, int processPid) { + return findClient(device, processName, processPid, defaultTimeout); + } + + /** + * Find the client with the given process name or process id. The name takes precedence over + * the process id (if valid). Stop looking after the given timeout. + * + * @param device The device to communicate with. + * @param processName The name of the process. May be null. + * @param processPid The pid of the process. Values less than or equal to zero are ignored. + * @param timeout The amount of milliseconds to wait, at most. + * @return The client, if found. Otherwise null. + */ + public Client findClient(IDevice device, String processName, int processPid, int timeout) { + WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout); + return wfc.get(); + } + + /** + * Shortcut for findAllClients with default timeout. + */ + public Client[] findAllClients(IDevice device) { + return findAllClients(device, defaultTimeout); + } + + /** + * Retrieve all clients known to the given device. Wait at most the given timeout. + * + * @param device The device to investigate. + * @param timeout The amount of milliseconds to wait, at most. + * @return An array of clients running on the given device. May be null depending on the + * device implementation. + */ + public Client[] findAllClients(IDevice device, int timeout) { + if (device.hasClients()) { + return device.getClients(); + } + WaitForClients wfc = new WaitForClients(device, timeout); + return wfc.get(); + } + + private static class WaitForClient implements IClientChangeListener { + + private IDevice device; + private String processName; + private int processPid; + private long timeout; + private Client result; + + public WaitForClient(IDevice device, String processName, int processPid, long timeout) { + this.device = device; + this.processName = processName; + this.processPid = processPid; + this.timeout = timeout; + this.result = null; + } + + public Client get() { + synchronized (this) { + AndroidDebugBridge.addClientChangeListener(this); + + // Maybe it's already there. + if (result == null) { + result = searchForClient(device); + } + + if (result == null) { + try { + wait(timeout); + } catch (InterruptedException e) { + // Note: doesn't guard for spurious wakeup. + } + } + } + + AndroidDebugBridge.removeClientChangeListener(this); + return result; + } + + private Client searchForClient(IDevice device) { + if (processName != null) { + Client tmp = device.getClient(processName); + if (tmp != null) { + return tmp; + } + } + if (processPid > 0) { + String name = device.getClientName(processPid); + if (name != null && !name.isEmpty()) { + Client tmp = device.getClient(name); + if (tmp != null) { + return tmp; + } + } + } + if (processPid > 0) { + // Try manual search. + for (Client cl : device.getClients()) { + if (cl.getClientData().getPid() == processPid + && cl.getClientData().getClientDescription() != null) { + return cl; + } + } + } + return null; + } + + private boolean isTargetClient(Client c) { + if (processPid > 0 && c.getClientData().getPid() == processPid) { + return true; + } + if (processName != null + && processName.equals(c.getClientData().getClientDescription())) { + return true; + } + return false; + } + + @Override + public void clientChanged(Client arg0, int arg1) { + synchronized (this) { + if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) { + if (isTargetClient(arg0)) { + result = arg0; + notifyAll(); + } + } + } + } + } + + private static class WaitForClients implements IClientChangeListener { + + private IDevice device; + private long timeout; + + public WaitForClients(IDevice device, long timeout) { + this.device = device; + this.timeout = timeout; + } + + public Client[] get() { + synchronized (this) { + AndroidDebugBridge.addClientChangeListener(this); + + if (device.hasClients()) { + return device.getClients(); + } + + try { + wait(timeout); // Note: doesn't guard for spurious wakeup. + } catch (InterruptedException exc) { + } + + // We will be woken up when the first client data arrives. Sleep a little longer + // to give (hopefully all of) the rest of the clients a chance to become available. + // Note: a loop with timeout is brittle as well and complicated, just accept this + // for now. + try { + Thread.sleep(500); + } catch (InterruptedException exc) { + } + } + + AndroidDebugBridge.removeClientChangeListener(this); + + return device.getClients(); + } + + @Override + public void clientChanged(Client arg0, int arg1) { + synchronized (this) { + if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) { + notifyAll(); + } + } + } + } +} diff --git a/tools/preload2/src/com/android/preload/DeviceUtils.java b/tools/preload2/src/com/android/preload/DeviceUtils.java new file mode 100644 index 000000000000..72de7b58b0a7 --- /dev/null +++ b/tools/preload2/src/com/android/preload/DeviceUtils.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload; + +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; +import com.android.preload.classdataretrieval.hprof.Hprof; +import com.android.ddmlib.DdmPreferences; +import com.android.ddmlib.IDevice; +import com.android.ddmlib.IShellOutputReceiver; + +import java.util.Date; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Helper class for some device routines. + */ +public class DeviceUtils { + + public static void init(int debugPort) { + DdmPreferences.setSelectedDebugPort(debugPort); + + Hprof.init(); + + AndroidDebugBridge.init(true); + + AndroidDebugBridge.createBridge(); + } + + /** + * Run a command in the shell on the device. + */ + public static void doShell(IDevice device, String cmdline, long timeout, TimeUnit unit) { + doShell(device, cmdline, new NullShellOutputReceiver(), timeout, unit); + } + + /** + * Run a command in the shell on the device. Collects and returns the console output. + */ + public static String doShellReturnString(IDevice device, String cmdline, long timeout, + TimeUnit unit) { + CollectStringShellOutputReceiver rec = new CollectStringShellOutputReceiver(); + doShell(device, cmdline, rec, timeout, unit); + return rec.toString(); + } + + /** + * Run a command in the shell on the device, directing all output to the given receiver. + */ + public static void doShell(IDevice device, String cmdline, IShellOutputReceiver receiver, + long timeout, TimeUnit unit) { + try { + device.executeShellCommand(cmdline, receiver, timeout, unit); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Run am start on the device. + */ + public static void doAMStart(IDevice device, String name, String activity) { + doShell(device, "am start -n " + name + " /." + activity, 30, TimeUnit.SECONDS); + } + + /** + * Find the device with the given serial. Give up after the given timeout (in milliseconds). + */ + public static IDevice findDevice(String serial, int timeout) { + WaitForDevice wfd = new WaitForDevice(serial, timeout); + return wfd.get(); + } + + /** + * Get all devices ddms knows about. Wait at most for the given timeout. + */ + public static IDevice[] findDevices(int timeout) { + WaitForDevice wfd = new WaitForDevice(null, timeout); + wfd.get(); + return AndroidDebugBridge.getBridge().getDevices(); + } + + /** + * Return the build type of the given device. This is the value of the "ro.build.type" + * system property. + */ + public static String getBuildType(IDevice device) { + try { + Future<String> buildType = device.getSystemProperty("ro.build.type"); + return buildType.get(500, TimeUnit.MILLISECONDS); + } catch (Exception e) { + } + return null; + } + + /** + * Check whether the given device has a pre-optimized boot image. More precisely, checks + * whether /system/framework/ * /boot.art exists. + */ + public static boolean hasPrebuiltBootImage(IDevice device) { + String ret = + doShellReturnString(device, "ls /system/framework/*/boot.art", 500, TimeUnit.MILLISECONDS); + + return !ret.contains("No such file or directory"); + } + + /** + * Remove files involved in a standard build that interfere with collecting data. This will + * remove /etc/preloaded-classes, which determines which classes are allocated already in the + * boot image. It also deletes any compiled boot image on the device. Then it restarts the + * device. + * + * This is a potentially long-running operation, as the boot after the deletion may take a while. + * The method will abort after the given timeout. + */ + public static boolean removePreloaded(IDevice device, long preloadedWaitTimeInSeconds) { + String oldContent = + DeviceUtils.doShellReturnString(device, "cat /etc/preloaded-classes", 1, TimeUnit.SECONDS); + if (oldContent.trim().equals("")) { + System.out.println("Preloaded-classes already empty."); + return true; + } + + // Stop the system server etc. + doShell(device, "stop", 100, TimeUnit.MILLISECONDS); + + // Remount /system, delete /etc/preloaded-classes. It would be nice to use "adb remount," + // but AndroidDebugBridge doesn't expose it. + doShell(device, "mount -o remount,rw /system", 500, TimeUnit.MILLISECONDS); + doShell(device, "rm /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS); + // We do need an empty file. + doShell(device, "touch /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS); + + // Delete the files in the dalvik cache. + doShell(device, "rm /data/dalvik-cache/*/*boot.art", 500, TimeUnit.MILLISECONDS); + + // We'll try to use dev.bootcomplete to know when the system server is back up. But stop + // doesn't reset it, so do it manually. + doShell(device, "setprop dev.bootcomplete \"0\"", 500, TimeUnit.MILLISECONDS); + + // Start the system server. + doShell(device, "start", 100, TimeUnit.MILLISECONDS); + + // Do a loop checking each second whether bootcomplete. Wait for at most the given + // threshold. + Date startDate = new Date(); + for (;;) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Ignore spurious wakeup. + } + // Check whether bootcomplete. + String ret = + doShellReturnString(device, "getprop dev.bootcomplete", 500, TimeUnit.MILLISECONDS); + if (ret.trim().equals("1")) { + break; + } + System.out.println("Still not booted: " + ret); + + // Check whether we timed out. This is a simplistic check that doesn't take into account + // things like switches in time. + Date endDate = new Date(); + long seconds = + TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS); + if (seconds > preloadedWaitTimeInSeconds) { + return false; + } + } + + return true; + } + + /** + * Enable method-tracing on device. The system should be restarted after this. + */ + public static void enableTracing(IDevice device) { + // Disable selinux. + doShell(device, "setenforce 0", 100, TimeUnit.MILLISECONDS); + + // Make the profile directory world-writable. + doShell(device, "chmod 777 /data/dalvik-cache/profiles", 100, TimeUnit.MILLISECONDS); + + // Enable streaming method tracing with a small 1K buffer. + doShell(device, "setprop dalvik.vm.method-trace true", 100, TimeUnit.MILLISECONDS); + doShell(device, "setprop dalvik.vm.method-trace-file " + + "/data/dalvik-cache/profiles/zygote.trace.bin", 100, TimeUnit.MILLISECONDS); + doShell(device, "setprop dalvik.vm.method-trace-file-siz 1024", 100, TimeUnit.MILLISECONDS); + doShell(device, "setprop dalvik.vm.method-trace-stream true", 100, TimeUnit.MILLISECONDS); + } + + private static class NullShellOutputReceiver implements IShellOutputReceiver { + @Override + public boolean isCancelled() { + return false; + } + + @Override + public void flush() {} + + @Override + public void addOutput(byte[] arg0, int arg1, int arg2) {} + } + + private static class CollectStringShellOutputReceiver implements IShellOutputReceiver { + + private StringBuilder builder = new StringBuilder(); + + @Override + public String toString() { + String ret = builder.toString(); + // Strip trailing newlines. They are especially ugly because adb uses DOS line endings. + while (ret.endsWith("\r") || ret.endsWith("\n")) { + ret = ret.substring(0, ret.length() - 1); + } + return ret; + } + + @Override + public void addOutput(byte[] arg0, int arg1, int arg2) { + builder.append(new String(arg0, arg1, arg2)); + } + + @Override + public void flush() {} + + @Override + public boolean isCancelled() { + return false; + } + } + + private static class WaitForDevice { + + private String serial; + private long timeout; + private IDevice device; + + public WaitForDevice(String serial, long timeout) { + this.serial = serial; + this.timeout = timeout; + device = null; + } + + public IDevice get() { + if (device == null) { + WaitForDeviceListener wfdl = new WaitForDeviceListener(serial); + synchronized (wfdl) { + AndroidDebugBridge.addDeviceChangeListener(wfdl); + + // Check whether we already know about this device. + IDevice[] devices = AndroidDebugBridge.getBridge().getDevices(); + if (serial != null) { + for (IDevice d : devices) { + if (serial.equals(d.getSerialNumber())) { + // Only accept if there are clients already. Else wait for the callback informing + // us that we now have clients. + if (d.hasClients()) { + device = d; + } + + break; + } + } + } else { + if (devices.length > 0) { + device = devices[0]; + } + } + + if (device == null) { + try { + wait(timeout); + } catch (InterruptedException e) { + // Ignore spurious wakeups. + } + device = wfdl.getDevice(); + } + + AndroidDebugBridge.removeDeviceChangeListener(wfdl); + } + } + + if (device != null) { + // Wait for clients. + WaitForClientsListener wfcl = new WaitForClientsListener(device); + synchronized (wfcl) { + AndroidDebugBridge.addDeviceChangeListener(wfcl); + + if (!device.hasClients()) { + try { + wait(timeout); + } catch (InterruptedException e) { + // Ignore spurious wakeups. + } + } + + AndroidDebugBridge.removeDeviceChangeListener(wfcl); + } + } + + return device; + } + + private static class WaitForDeviceListener implements IDeviceChangeListener { + + private String serial; + private IDevice device; + + public WaitForDeviceListener(String serial) { + this.serial = serial; + } + + public IDevice getDevice() { + return device; + } + + @Override + public void deviceChanged(IDevice arg0, int arg1) { + // We may get a device changed instead of connected. Handle like a connection. + deviceConnected(arg0); + } + + @Override + public void deviceConnected(IDevice arg0) { + if (device != null) { + // Ignore updates. + return; + } + + if (serial == null || serial.equals(arg0.getSerialNumber())) { + device = arg0; + synchronized (this) { + notifyAll(); + } + } + } + + @Override + public void deviceDisconnected(IDevice arg0) { + // Ignore disconnects. + } + + } + + private static class WaitForClientsListener implements IDeviceChangeListener { + + private IDevice myDevice; + + public WaitForClientsListener(IDevice myDevice) { + this.myDevice = myDevice; + } + + @Override + public void deviceChanged(IDevice arg0, int arg1) { + if (arg0 == myDevice && (arg1 & IDevice.CHANGE_CLIENT_LIST) != 0) { + // Got a client list, done here. + synchronized (this) { + notifyAll(); + } + } + } + + @Override + public void deviceConnected(IDevice arg0) { + } + + @Override + public void deviceDisconnected(IDevice arg0) { + } + + } + } + +} diff --git a/tools/preload2/src/com/android/preload/DumpData.java b/tools/preload2/src/com/android/preload/DumpData.java new file mode 100644 index 000000000000..d99722416a1d --- /dev/null +++ b/tools/preload2/src/com/android/preload/DumpData.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Holds the collected data for a process. + */ +public class DumpData { + /** + * Name of the package (=application). + */ + String packageName; + + /** + * A map of class name to a string for the classloader. This may be a toString equivalent, + * or just a unique ID. + */ + Map<String, String> dumpData; + + /** + * The Date when this data was captured. Mostly for display purposes. + */ + Date date; + + /** + * A cached value for the number of boot classpath classes (classloader value in dumpData is + * null). + */ + int bcpClasses; + + public DumpData(String packageName, Map<String, String> dumpData, Date date) { + this.packageName = packageName; + this.dumpData = dumpData; + this.date = date; + + countBootClassPath(); + } + + public String getPackageName() { + return packageName; + } + + public Date getDate() { + return date; + } + + public Map<String, String> getDumpData() { + return dumpData; + } + + public void countBootClassPath() { + bcpClasses = 0; + for (Map.Entry<String, String> e : dumpData.entrySet()) { + if (e.getValue() == null) { + bcpClasses++; + } + } + } + + // Return an inverted mapping. + public Map<String, Set<String>> invertData() { + Map<String, Set<String>> ret = new HashMap<>(); + for (Map.Entry<String, String> e : dumpData.entrySet()) { + if (!ret.containsKey(e.getValue())) { + ret.put(e.getValue(), new HashSet<String>()); + } + ret.get(e.getValue()).add(e.getKey()); + } + return ret; + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/DumpDataIO.java b/tools/preload2/src/com/android/preload/DumpDataIO.java new file mode 100644 index 000000000000..28625c5531e4 --- /dev/null +++ b/tools/preload2/src/com/android/preload/DumpDataIO.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.File; +import java.io.FileReader; +import java.text.DateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * Helper class for serialization and deserialization of a collection of DumpData objects to XML. + */ +public class DumpDataIO { + + /** + * Serialize the given collection to an XML document. Returns the produced string. + */ + public static String serialize(Collection<DumpData> data) { + // We'll do this by hand, constructing a DOM or similar is too complicated for our simple + // use case. + + StringBuilder sb = new StringBuilder(); + sb.append("<preloaded-classes-data>\n"); + + for (DumpData d : data) { + serialize(d, sb); + } + + sb.append("</preloaded-classes-data>\n"); + return sb.toString(); + } + + private static void serialize(DumpData d, StringBuilder sb) { + sb.append("<data package=\"" + d.packageName + "\" date=\"" + + DateFormat.getDateTimeInstance().format(d.date) +"\">\n"); + + for (Map.Entry<String, String> e : d.dumpData.entrySet()) { + sb.append("<class name=\"" + e.getKey() + "\" classloader=\"" + e.getValue() + "\"/>\n"); + } + + sb.append("</data>\n"); + } + + /** + * Load a collection of DumpData objects from the given file. + */ + public static Collection<DumpData> deserialize(File f) throws Exception { + // Use SAX parsing. Our format is very simple. Don't do any schema validation or such. + + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setNamespaceAware(false); + SAXParser saxParser = spf.newSAXParser(); + + XMLReader xmlReader = saxParser.getXMLReader(); + DumpDataContentHandler ddch = new DumpDataContentHandler(); + xmlReader.setContentHandler(ddch); + xmlReader.parse(new InputSource(new FileReader(f))); + + return ddch.data; + } + + private static class DumpDataContentHandler extends DefaultHandler { + Collection<DumpData> data = new LinkedList<DumpData>(); + DumpData openData = null; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (qName.equals("data")) { + if (openData != null) { + throw new IllegalStateException(); + } + String pkg = attributes.getValue("package"); + String dateString = attributes.getValue("date"); + + if (pkg == null || dateString == null) { + throw new IllegalArgumentException(); + } + + try { + Date date = DateFormat.getDateTimeInstance().parse(dateString); + openData = new DumpData(pkg, new HashMap<String, String>(), date); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else if (qName.equals("class")) { + if (openData == null) { + throw new IllegalStateException(); + } + String className = attributes.getValue("name"); + String classLoader = attributes.getValue("classloader"); + + if (className == null || classLoader == null) { + throw new IllegalArgumentException(); + } + + openData.dumpData.put(className, classLoader.equals("null") ? null : classLoader); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals("data")) { + if (openData == null) { + throw new IllegalStateException(); + } + openData.countBootClassPath(); + + data.add(openData); + openData = null; + } + } + } +} diff --git a/tools/preload2/src/com/android/preload/DumpTableModel.java b/tools/preload2/src/com/android/preload/DumpTableModel.java new file mode 100644 index 000000000000..d97cbf0df5e5 --- /dev/null +++ b/tools/preload2/src/com/android/preload/DumpTableModel.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload; + +import java.util.ArrayList; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +/** + * A table model for collected DumpData. This is both the internal storage as well as the model + * for display. + */ +public class DumpTableModel extends AbstractTableModel { + + private List<DumpData> data = new ArrayList<DumpData>(); + + public void addData(DumpData d) { + data.add(d); + fireTableRowsInserted(data.size() - 1, data.size() - 1); + } + + public void clear() { + int size = data.size(); + if (size > 0) { + data.clear(); + fireTableRowsDeleted(0, size - 1); + } + } + + public List<DumpData> getData() { + return data; + } + + @Override + public int getRowCount() { + return data.size(); + } + + @Override + public int getColumnCount() { + return 4; + } + + @Override + public String getColumnName(int column) { + switch (column) { + case 0: + return "Package"; + case 1: + return "Date"; + case 2: + return "# All Classes"; + case 3: + return "# Boot Classpath Classes"; + + default: + throw new IndexOutOfBoundsException(String.valueOf(column)); + } + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + DumpData d = data.get(rowIndex); + switch (columnIndex) { + case 0: + return d.packageName; + case 1: + return d.date; + case 2: + return d.dumpData.size(); + case 3: + return d.bcpClasses; + + default: + throw new IndexOutOfBoundsException(String.valueOf(columnIndex)); + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java new file mode 100644 index 000000000000..ca5b0e005a1d --- /dev/null +++ b/tools/preload2/src/com/android/preload/Main.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.preload.actions.ClearTableAction; +import com.android.preload.actions.ComputeThresholdAction; +import com.android.preload.actions.ComputeThresholdXAction; +import com.android.preload.actions.DeviceSpecific; +import com.android.preload.actions.ExportAction; +import com.android.preload.actions.ImportAction; +import com.android.preload.actions.ReloadListAction; +import com.android.preload.actions.RunMonkeyAction; +import com.android.preload.actions.ScanAllPackagesAction; +import com.android.preload.actions.ScanPackageAction; +import com.android.preload.actions.ShowDataAction; +import com.android.preload.classdataretrieval.ClassDataRetriever; +import com.android.preload.classdataretrieval.hprof.Hprof; +import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever; +import com.android.preload.ui.UI; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.swing.Action; +import javax.swing.DefaultListModel; + +public class Main { + + /** + * Enable tracing mode. This is a work-in-progress to derive compiled-methods data, so it is + * off for now. + */ + public final static boolean ENABLE_TRACING = false; + + /** + * Ten-second timeout. + */ + public final static int DEFAULT_TIMEOUT_MILLIS = 10 * 1000; + + /** + * Hprof timeout. Two minutes. + */ + public final static int HPROF_TIMEOUT_MILLIS = 120 * 1000; + + private IDevice device; + private static ClientUtils clientUtils; + + private DumpTableModel dataTableModel; + private DefaultListModel<Client> clientListModel; + + private UI ui; + + // Actions that need to be updated once a device is selected. + private Collection<DeviceSpecific> deviceSpecificActions; + + // Current main instance. + private static Main top; + private static boolean useJdwpClassDataRetriever = false; + + public final static String CLASS_PRELOAD_BLACKLIST = "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$" + "|" + + "(.*\\$NoPreloadHolder$)"; + + /** + * @param args + */ + public static void main(String[] args) { + Main m = new Main(); + top = m; + + m.startUp(); + } + + public Main() { + clientListModel = new DefaultListModel<Client>(); + dataTableModel = new DumpTableModel(); + + clientUtils = new ClientUtils(DEFAULT_TIMEOUT_MILLIS); // Client utils with 10s timeout. + + List<Action> actions = new ArrayList<Action>(); + actions.add(new ReloadListAction(clientUtils, null, clientListModel)); + actions.add(new ClearTableAction(dataTableModel)); + actions.add(new RunMonkeyAction(null, dataTableModel)); + actions.add(new ScanPackageAction(clientUtils, null, dataTableModel)); + actions.add(new ScanAllPackagesAction(clientUtils, null, dataTableModel)); + actions.add(new ComputeThresholdAction("Compute preloaded-classes", dataTableModel, 2, + CLASS_PRELOAD_BLACKLIST)); + actions.add(new ComputeThresholdAction("Compute compiled-classes", dataTableModel, 1, + null)); + actions.add(new ComputeThresholdXAction("Compute(X)", dataTableModel, + CLASS_PRELOAD_BLACKLIST)); + actions.add(new ShowDataAction(dataTableModel)); + actions.add(new ImportAction(dataTableModel)); + actions.add(new ExportAction(dataTableModel)); + + deviceSpecificActions = new ArrayList<DeviceSpecific>(); + for (Action a : actions) { + if (a instanceof DeviceSpecific) { + deviceSpecificActions.add((DeviceSpecific)a); + } + } + + ui = new UI(clientListModel, dataTableModel, actions); + ui.setVisible(true); + } + + public static UI getUI() { + return top.ui; + } + + public static ClassDataRetriever getClassDataRetriever() { + if (useJdwpClassDataRetriever) { + return new JDWPClassDataRetriever(); + } else { + return new Hprof(HPROF_TIMEOUT_MILLIS); + } + } + + public IDevice getDevice() { + return device; + } + + public void setDevice(IDevice device) { + this.device = device; + for (DeviceSpecific ds : deviceSpecificActions) { + ds.setDevice(device); + } + } + + public DefaultListModel<Client> getClientListModel() { + return clientListModel; + } + + static class DeviceWrapper { + IDevice device; + + public DeviceWrapper(IDevice d) { + device = d; + } + + @Override + public String toString() { + return device.getName() + " (#" + device.getSerialNumber() + ")"; + } + } + + private void startUp() { + getUI().showWaitDialog(); + initDevice(); + + // Load clients. + new ReloadListAction(clientUtils, getDevice(), clientListModel).run(); + + getUI().hideWaitDialog(); + } + + private void initDevice() { + DeviceUtils.init(DEFAULT_TIMEOUT_MILLIS); + + IDevice devices[] = DeviceUtils.findDevices(DEFAULT_TIMEOUT_MILLIS); + if (devices == null || devices.length == 0) { + throw new RuntimeException("Could not find any devices..."); + } + + getUI().hideWaitDialog(); + + DeviceWrapper deviceWrappers[] = new DeviceWrapper[devices.length]; + for (int i = 0; i < devices.length; i++) { + deviceWrappers[i] = new DeviceWrapper(devices[i]); + } + + DeviceWrapper ret = Main.getUI().showChoiceDialog("Choose a device", "Choose device", + deviceWrappers); + if (ret != null) { + setDevice(ret.device); + } else { + System.exit(0); + } + + boolean prepare = Main.getUI().showConfirmDialog("Prepare device?", + "Do you want to prepare the device? This is highly recommended."); + if (prepare) { + String buildType = DeviceUtils.getBuildType(device); + if (buildType == null || (!buildType.equals("userdebug") && !buildType.equals("eng"))) { + Main.getUI().showMessageDialog("Need a userdebug or eng build! (Found " + buildType + + ")"); + return; + } + if (DeviceUtils.hasPrebuiltBootImage(device)) { + Main.getUI().showMessageDialog("Cannot prepare a device with pre-optimized boot " + + "image!"); + return; + } + + if (ENABLE_TRACING) { + DeviceUtils.enableTracing(device); + } + + Main.getUI().showMessageDialog("The device will reboot. This will potentially take a " + + "long time. Please be patient."); + if (!DeviceUtils.removePreloaded(device, 15 * 60) /* 15m timeout */) { + Main.getUI().showMessageDialog("Removing preloaded-classes failed unexpectedly!"); + } + } + } + + public static Map<String, String> findAndGetClassData(IDevice device, String packageName) + throws Exception { + Client client = clientUtils.findClient(device, packageName, -1); + if (client == null) { + throw new RuntimeException("Could not find client..."); + } + System.out.println("Found client: " + client); + + return getClassDataRetriever().getClassData(client); + } + +} diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java new file mode 100644 index 000000000000..fbf83d2e2339 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import java.awt.event.ActionEvent; + +import javax.swing.AbstractAction; + +public abstract class AbstractThreadedAction extends AbstractAction implements Runnable { + + protected AbstractThreadedAction(String title) { + super(title); + } + + @Override + public void actionPerformed(ActionEvent e) { + new Thread(this).start(); + } + +} diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java new file mode 100644 index 000000000000..7906417b7a8d --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedDeviceSpecificAction.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.ddmlib.IDevice; + +import java.awt.event.ActionEvent; + +public abstract class AbstractThreadedDeviceSpecificAction extends AbstractThreadedAction + implements DeviceSpecific { + + protected IDevice device; + + protected AbstractThreadedDeviceSpecificAction(String title, IDevice device) { + super(title); + this.device = device; + } + + @Override + public void setDevice(IDevice device) { + this.device = device; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (device == null) { + return; + } + super.actionPerformed(e); + } +} diff --git a/tools/preload2/src/com/android/preload/actions/ClearTableAction.java b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java new file mode 100644 index 000000000000..c0e4795b6d90 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ClearTableAction.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.preload.DumpTableModel; + +import java.awt.event.ActionEvent; + +import javax.swing.AbstractAction; + +public class ClearTableAction extends AbstractAction { + private final DumpTableModel dataTableModel; + + public ClearTableAction(DumpTableModel dataTableModel) { + super("Clear"); + this.dataTableModel = dataTableModel; + } + + @Override + public void actionPerformed(ActionEvent e) { + dataTableModel.clear(); + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java new file mode 100644 index 000000000000..b524716fc2cb --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Pattern; + +import javax.swing.AbstractAction; +import javax.swing.JFileChooser; + +/** + * Compute an intersection of classes from the given data. A class is in the intersection if it + * appears in at least the number of threshold given packages. An optional blacklist can be + * used to filter classes from the intersection. + */ +public class ComputeThresholdAction extends AbstractAction implements Runnable { + protected int threshold; + private Pattern blacklist; + private DumpTableModel dataTableModel; + + /** + * Create an action with the given parameters. The blacklist is a regular expression + * that filters classes. + */ + public ComputeThresholdAction(String name, DumpTableModel dataTableModel, int threshold, + String blacklist) { + super(name); + this.dataTableModel = dataTableModel; + this.threshold = threshold; + if (blacklist != null) { + this.blacklist = Pattern.compile(blacklist); + } + } + + @Override + public void actionPerformed(ActionEvent e) { + List<DumpData> data = dataTableModel.getData(); + if (data.size() == 0) { + Main.getUI().showMessageDialog("No data available, please scan packages or run " + + "monkeys."); + return; + } + if (data.size() == 1) { + Main.getUI().showMessageDialog("Cannot compute list from only one data set, please " + + "scan packages or run monkeys."); + return; + } + + new Thread(this).start(); + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + Map<String, Set<String>> uses = new HashMap<String, Set<String>>(); + for (DumpData d : dataTableModel.getData()) { + Main.getUI().updateWaitDialog("Merging " + d.getPackageName()); + updateClassUse(d.getPackageName(), uses, getBootClassPathClasses(d.getDumpData())); + } + + Main.getUI().updateWaitDialog("Computing thresholded set"); + Set<String> result = fromThreshold(uses, blacklist, threshold); + Main.getUI().hideWaitDialog(); + + boolean ret = Main.getUI().showConfirmDialog("Computed a set with " + result.size() + + " classes, would you like to save to disk?", "Save?"); + if (ret) { + JFileChooser jfc = new JFileChooser(); + int ret2 = jfc.showSaveDialog(Main.getUI()); + if (ret2 == JFileChooser.APPROVE_OPTION) { + File f = jfc.getSelectedFile(); + saveSet(result, f); + } + } + } + + private Set<String> fromThreshold(Map<String, Set<String>> classUses, Pattern blacklist, + int threshold) { + TreeSet<String> ret = new TreeSet<>(); // TreeSet so it's nicely ordered by name. + + for (Map.Entry<String, Set<String>> e : classUses.entrySet()) { + if (e.getValue().size() >= threshold) { + if (blacklist == null || !blacklist.matcher(e.getKey()).matches()) { + ret.add(e.getKey()); + } + } + } + + return ret; + } + + private static void updateClassUse(String pkg, Map<String, Set<String>> classUses, + Set<String> classes) { + for (String className : classes) { + Set<String> old = classUses.get(className); + if (old == null) { + classUses.put(className, new HashSet<String>()); + } + classUses.get(className).add(pkg); + } + } + + private static Set<String> getBootClassPathClasses(Map<String, String> source) { + Set<String> ret = new HashSet<>(); + for (Map.Entry<String, String> e : source.entrySet()) { + if (e.getValue() == null) { + ret.add(e.getKey()); + } + } + return ret; + } + + private static void saveSet(Set<String> result, File f) { + try { + PrintWriter out = new PrintWriter(f); + for (String s : result) { + out.println(s); + } + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java new file mode 100644 index 000000000000..3ec0a4c18db1 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdXAction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +public class ComputeThresholdXAction extends ComputeThresholdAction { + + public ComputeThresholdXAction(String name, DumpTableModel dataTableModel, + String blacklist) { + super(name, dataTableModel, 1, blacklist); + } + + @Override + public void run() { + String value = Main.getUI().showInputDialog("Threshold?"); + + if (value != null) { + try { + threshold = Integer.parseInt(value); + super.run(); + } catch (Exception exc) { + } + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java new file mode 100644 index 000000000000..35a8f26a99fe --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/DeviceSpecific.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.ddmlib.IDevice; + +/** + * Marks an action as being device-specific. The user must set the device through the specified + * method if the device selection changes. + * + * Implementors must tolerate a null device (for example, with a no-op). This includes calling + * any methods before setDevice has been called. + */ +public interface DeviceSpecific { + + /** + * Set the device that should be used. Note that there is no restriction on calling other + * methods of the implementor before a setDevice call. Neither is device guaranteed to be + * non-null. + * + * @param device The device to use going forward. + */ + public void setDevice(IDevice device); +} diff --git a/tools/preload2/src/com/android/preload/actions/ExportAction.java b/tools/preload2/src/com/android/preload/actions/ExportAction.java new file mode 100644 index 000000000000..cb8b3df75b18 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ExportAction.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.preload.DumpDataIO; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.PrintWriter; + +import javax.swing.AbstractAction; + +public class ExportAction extends AbstractAction implements Runnable { + private File lastSaveFile; + private DumpTableModel dataTableModel; + + public ExportAction(DumpTableModel dataTableModel) { + super("Export data"); + this.dataTableModel = dataTableModel; + } + + @Override + public void actionPerformed(ActionEvent e) { + lastSaveFile = Main.getUI().showSaveDialog(); + if (lastSaveFile != null) { + new Thread(this).start(); + } + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + String serialized = DumpDataIO.serialize(dataTableModel.getData()); + + if (serialized != null) { + try { + PrintWriter out = new PrintWriter(lastSaveFile); + out.println(serialized); + out.close(); + + Main.getUI().hideWaitDialog(); + } catch (Exception e) { + Main.getUI().hideWaitDialog(); + Main.getUI().showMessageDialog("Failed writing: " + e.getMessage()); + } + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ImportAction.java b/tools/preload2/src/com/android/preload/actions/ImportAction.java new file mode 100644 index 000000000000..5c1976580f94 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ImportAction.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.preload.DumpData; +import com.android.preload.DumpDataIO; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.Collection; + +import javax.swing.AbstractAction; + +public class ImportAction extends AbstractAction implements Runnable { + private File[] lastOpenFiles; + private DumpTableModel dataTableModel; + + public ImportAction(DumpTableModel dataTableModel) { + super("Import data"); + this.dataTableModel = dataTableModel; + } + + @Override + public void actionPerformed(ActionEvent e) { + lastOpenFiles = Main.getUI().showOpenDialog(true); + if (lastOpenFiles != null) { + new Thread(this).start(); + } + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + try { + for (File f : lastOpenFiles) { + try { + Collection<DumpData> data = DumpDataIO.deserialize(f); + + for (DumpData d : data) { + dataTableModel.addData(d); + } + } catch (Exception e) { + Main.getUI().showMessageDialog("Failed reading: " + e.getMessage()); + } + } + } finally { + Main.getUI().hideWaitDialog(); + } + + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ReloadListAction.java b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java new file mode 100644 index 000000000000..29f055700dfa --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ReloadListAction.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.preload.ClientUtils; + +import java.util.Arrays; +import java.util.Comparator; + +import javax.swing.DefaultListModel; + +public class ReloadListAction extends AbstractThreadedDeviceSpecificAction { + + private ClientUtils clientUtils; + private final DefaultListModel<Client> clientListModel; + + public ReloadListAction(ClientUtils utils, IDevice device, + DefaultListModel<Client> clientListModel) { + super("Reload", device); + this.clientUtils = utils; + this.clientListModel = clientListModel; + } + + @Override + public void run() { + Client[] clients = clientUtils.findAllClients(device); + if (clients != null) { + Arrays.sort(clients, new ClientComparator()); + } + clientListModel.removeAllElements(); + for (Client c : clients) { + clientListModel.addElement(c); + } + } + + private static class ClientComparator implements Comparator<Client> { + + @Override + public int compare(Client o1, Client o2) { + String s1 = o1.getClientData().getClientDescription(); + String s2 = o2.getClientData().getClientDescription(); + + if (s1 == null || s2 == null) { + // Not good, didn't get all data? + return (s1 == null) ? -1 : 1; + } + + return s1.compareTo(s2); + } + + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java new file mode 100644 index 000000000000..385e8577b1c8 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.ddmlib.IDevice; +import com.android.preload.DeviceUtils; +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.event.ActionEvent; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.swing.AbstractAction; + +public class RunMonkeyAction extends AbstractAction implements DeviceSpecific { + + private final static String DEFAULT_MONKEY_PACKAGES = + "com.android.calendar,com.android.gallery3d"; + + private IDevice device; + private DumpTableModel dataTableModel; + + public RunMonkeyAction(IDevice device, DumpTableModel dataTableModel) { + super("Run monkey"); + this.device = device; + this.dataTableModel = dataTableModel; + } + + @Override + public void setDevice(IDevice device) { + this.device = device; + } + + @Override + public void actionPerformed(ActionEvent e) { + String packages = Main.getUI().showInputDialog("Please enter packages name to run with" + + " the monkey, or leave empty for default."); + if (packages == null) { + return; + } + if (packages.isEmpty()) { + packages = DEFAULT_MONKEY_PACKAGES; + } + new Thread(new RunMonkeyRunnable(packages)).start(); + } + + private class RunMonkeyRunnable implements Runnable { + + private String packages; + private final static int ITERATIONS = 1000; + + public RunMonkeyRunnable(String packages) { + this.packages = packages; + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + try { + String pkgs[] = packages.split(","); + + for (String pkg : pkgs) { + Main.getUI().updateWaitDialog("Running monkey on " + pkg); + + try { + // Stop running app. + forceStop(pkg); + + // Little bit of breather here. + try { + Thread.sleep(1000); + } catch (Exception e) { + } + + DeviceUtils.doShell(device, "monkey -p " + pkg + " " + ITERATIONS, 1, + TimeUnit.MINUTES); + + Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg); + Map<String, String> data = Main.findAndGetClassData(device, pkg); + DumpData dumpData = new DumpData(pkg, data, new Date()); + dataTableModel.addData(dumpData); + } catch (Exception e) { + e.printStackTrace(); + } finally { + // Stop running app. + forceStop(pkg); + } + } + } finally { + Main.getUI().hideWaitDialog(); + } + } + + private void forceStop(String packageName) { + // Stop running app. + DeviceUtils.doShell(device, "force-stop " + packageName, 5, TimeUnit.SECONDS); + DeviceUtils.doShell(device, "kill " + packageName, 5, TimeUnit.SECONDS); + DeviceUtils.doShell(device, "kill `pid " + packageName + "`", 5, TimeUnit.SECONDS); + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java new file mode 100644 index 000000000000..d74b8a3f6cfb --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ScanAllPackagesAction.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.preload.ClientUtils; +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.util.Date; +import java.util.Map; + +public class ScanAllPackagesAction extends AbstractThreadedDeviceSpecificAction { + + private ClientUtils clientUtils; + private DumpTableModel dataTableModel; + + public ScanAllPackagesAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) { + super("Scan all packages", device); + this.clientUtils = utils; + this.dataTableModel = dataTableModel; + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + try { + Client[] clients = clientUtils.findAllClients(device); + for (Client c : clients) { + String pkg = c.getClientData().getClientDescription(); + Main.getUI().showWaitDialog(); + Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg); + + try { + Map<String, String> data = Main.getClassDataRetriever().getClassData(c); + DumpData dumpData = new DumpData(pkg, data, new Date()); + dataTableModel.addData(dumpData); + } catch (Exception e) { + e.printStackTrace(); + } + } + } finally { + Main.getUI().hideWaitDialog(); + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java new file mode 100644 index 000000000000..98492bd951bf --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ScanPackageAction.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.ddmlib.Client; +import com.android.ddmlib.IDevice; +import com.android.preload.ClientUtils; +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.util.Date; +import java.util.Map; + +public class ScanPackageAction extends AbstractThreadedDeviceSpecificAction { + + private ClientUtils clientUtils; + private DumpTableModel dataTableModel; + + public ScanPackageAction(ClientUtils utils, IDevice device, DumpTableModel dataTableModel) { + super("Scan package", device); + this.clientUtils = utils; + this.dataTableModel = dataTableModel; + } + + @Override + public void run() { + Main.getUI().showWaitDialog(); + + try { + Client client = Main.getUI().getSelectedClient(); + if (client != null) { + work(client); + } else { + Client[] clients = clientUtils.findAllClients(device); + if (clients.length > 0) { + ClientWrapper[] clientWrappers = new ClientWrapper[clients.length]; + for (int i = 0; i < clientWrappers.length; i++) { + clientWrappers[i] = new ClientWrapper(clients[i]); + } + Main.getUI().hideWaitDialog(); + + ClientWrapper ret = Main.getUI().showChoiceDialog("Choose a package to scan", + "Choose package", + clientWrappers); + if (ret != null) { + work(ret.client); + } + } + } + } finally { + Main.getUI().hideWaitDialog(); + } + } + + private void work(Client c) { + String pkg = c.getClientData().getClientDescription(); + Main.getUI().showWaitDialog(); + Main.getUI().updateWaitDialog("Retrieving heap data for " + pkg); + + try { + Map<String, String> data = Main.findAndGetClassData(device, pkg); + DumpData dumpData = new DumpData(pkg, data, new Date()); + dataTableModel.addData(dumpData); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static class ClientWrapper { + private Client client; + + public ClientWrapper(Client c) { + client = c; + } + + @Override + public String toString() { + return client.getClientData().getClientDescription() + " (pid " + + client.getClientData().getPid() + ")"; + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/actions/ShowDataAction.java b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java new file mode 100644 index 000000000000..2bb175f60772 --- /dev/null +++ b/tools/preload2/src/com/android/preload/actions/ShowDataAction.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.actions; + +import com.android.preload.DumpData; +import com.android.preload.DumpTableModel; +import com.android.preload.Main; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.AbstractAction; +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +public class ShowDataAction extends AbstractAction { + private DumpTableModel dataTableModel; + + public ShowDataAction(DumpTableModel dataTableModel) { + super("Show data"); + this.dataTableModel = dataTableModel; + } + + @Override + public void actionPerformed(ActionEvent e) { + // TODO(agampe): Auto-generated method stub + int selRow = Main.getUI().getSelectedDataTableRow(); + if (selRow != -1) { + DumpData data = dataTableModel.getData().get(selRow); + Map<String, Set<String>> inv = data.invertData(); + + StringBuilder builder = new StringBuilder(); + + // First bootclasspath. + add(builder, "Boot classpath:", inv.get(null)); + + // Now everything else. + for (String k : inv.keySet()) { + if (k != null) { + builder.append("==================\n\n"); + add(builder, k, inv.get(k)); + } + } + + JFrame newFrame = new JFrame(data.getPackageName() + " " + data.getDate()); + newFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + newFrame.getContentPane().add(new JScrollPane(new JTextArea(builder.toString())), + BorderLayout.CENTER); + newFrame.setSize(800, 600); + newFrame.setLocationRelativeTo(null); + newFrame.setVisible(true); + } + } + + private void add(StringBuilder builder, String head, Set<String> set) { + builder.append(head); + builder.append('\n'); + addSet(builder, set); + builder.append('\n'); + } + + private void addSet(StringBuilder builder, Set<String> set) { + if (set == null) { + builder.append(" NONE\n"); + return; + } + List<String> sorted = new ArrayList<>(set); + Collections.sort(sorted); + for (String s : sorted) { + builder.append(s); + builder.append('\n'); + } + } +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java new file mode 100644 index 000000000000..f04360fc1942 --- /dev/null +++ b/tools/preload2/src/com/android/preload/classdataretrieval/ClassDataRetriever.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.classdataretrieval; + +import com.android.ddmlib.Client; + +import java.util.Map; + +/** + * Retrieve a class-to-classloader map for loaded classes from the client. + */ +public interface ClassDataRetriever { + + public Map<String, String> getClassData(Client client); +} diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java new file mode 100644 index 000000000000..8d797ee00128 --- /dev/null +++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/GeneralHprofDumpHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.classdataretrieval.hprof; + +import com.android.ddmlib.Client; +import com.android.ddmlib.ClientData.IHprofDumpHandler; + +import java.util.ArrayList; +import java.util.List; + +public class GeneralHprofDumpHandler implements IHprofDumpHandler { + + private List<IHprofDumpHandler> handlers = new ArrayList<>(); + + public void addHandler(IHprofDumpHandler h) { + synchronized (handlers) { + handlers.add(h); + } + } + + public void removeHandler(IHprofDumpHandler h) { + synchronized (handlers) { + handlers.remove(h); + } + } + + private List<IHprofDumpHandler> getIterationList() { + synchronized (handlers) { + return new ArrayList<>(handlers); + } + } + + @Override + public void onEndFailure(Client arg0, String arg1) { + List<IHprofDumpHandler> iterList = getIterationList(); + for (IHprofDumpHandler h : iterList) { + h.onEndFailure(arg0, arg1); + } + } + + @Override + public void onSuccess(String arg0, Client arg1) { + List<IHprofDumpHandler> iterList = getIterationList(); + for (IHprofDumpHandler h : iterList) { + h.onSuccess(arg0, arg1); + } + } + + @Override + public void onSuccess(byte[] arg0, Client arg1) { + List<IHprofDumpHandler> iterList = getIterationList(); + for (IHprofDumpHandler h : iterList) { + h.onSuccess(arg0, arg1); + } + } + }
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java new file mode 100644 index 000000000000..21b7a04e07dc --- /dev/null +++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.classdataretrieval.hprof; + +import com.android.ddmlib.Client; +import com.android.ddmlib.ClientData; +import com.android.ddmlib.ClientData.IHprofDumpHandler; +import com.android.preload.classdataretrieval.ClassDataRetriever; +import com.android.preload.ui.NullProgressMonitor; +import com.android.tools.perflib.captures.MemoryMappedFileBuffer; +import com.android.tools.perflib.heap.ClassObj; +import com.android.tools.perflib.heap.Queries; +import com.android.tools.perflib.heap.Snapshot; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class Hprof implements ClassDataRetriever { + + private static GeneralHprofDumpHandler hprofHandler; + + public static void init() { + synchronized(Hprof.class) { + if (hprofHandler == null) { + ClientData.setHprofDumpHandler(hprofHandler = new GeneralHprofDumpHandler()); + } + } + } + + public static File doHprof(Client client, int timeout) { + GetHprof gh = new GetHprof(client, timeout); + return gh.get(); + } + + /** + * Return a map of class names to class-loader names derived from the hprof dump. + * + * @param hprofLocalFile + */ + public static Map<String, String> analyzeHprof(File hprofLocalFile) throws Exception { + Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprofLocalFile)); + + Map<String, Set<ClassObj>> classes = Queries.classes(snapshot, null); + Map<String, String> retValue = new HashMap<String, String>(); + for (Map.Entry<String, Set<ClassObj>> e : classes.entrySet()) { + for (ClassObj c : e.getValue()) { + String cl = c.getClassLoader() == null ? null : c.getClassLoader().toString(); + String cName = c.getClassName(); + int aDepth = 0; + while (cName.endsWith("[]")) { + cName = cName.substring(0, cName.length()-2); + aDepth++; + } + String newName = transformPrimitiveClass(cName); + if (aDepth > 0) { + // Need to use kind-a descriptor syntax. If it was transformed, it is primitive. + if (newName.equals(cName)) { + newName = "L" + newName + ";"; + } + for (int i = 0; i < aDepth; i++) { + newName = "[" + newName; + } + } + retValue.put(newName, cl); + } + } + + // Free up memory. + snapshot.dispose(); + + return retValue; + } + + private static Map<String, String> primitiveMapping; + + static { + primitiveMapping = new HashMap<>(); + primitiveMapping.put("boolean", "Z"); + primitiveMapping.put("byte", "B"); + primitiveMapping.put("char", "C"); + primitiveMapping.put("double", "D"); + primitiveMapping.put("float", "F"); + primitiveMapping.put("int", "I"); + primitiveMapping.put("long", "J"); + primitiveMapping.put("short", "S"); + primitiveMapping.put("void", "V"); + } + + private static String transformPrimitiveClass(String name) { + String rep = primitiveMapping.get(name); + if (rep != null) { + return rep; + } + return name; + } + + private static class GetHprof implements IHprofDumpHandler { + + private File target; + private long timeout; + private Client client; + + public GetHprof(Client client, long timeout) { + this.client = client; + this.timeout = timeout; + } + + public File get() { + synchronized (this) { + hprofHandler.addHandler(this); + client.dumpHprof(); + if (target == null) { + try { + wait(timeout); + } catch (Exception e) { + System.out.println(e); + } + } + } + + hprofHandler.removeHandler(this); + return target; + } + + private void wakeUp() { + synchronized (this) { + notifyAll(); + } + } + + @Override + public void onEndFailure(Client arg0, String arg1) { + System.out.println("GetHprof.onEndFailure"); + if (client == arg0) { + wakeUp(); + } + } + + private static File createTargetFile() { + try { + return File.createTempFile("ddms", ".hprof"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void onSuccess(String arg0, Client arg1) { + System.out.println("GetHprof.onSuccess"); + if (client == arg1) { + try { + target = createTargetFile(); + arg1.getDevice().getSyncService().pullFile(arg0, + target.getAbsoluteFile().toString(), new NullProgressMonitor()); + } catch (Exception e) { + e.printStackTrace(); + target = null; + } + wakeUp(); + } + } + + @Override + public void onSuccess(byte[] arg0, Client arg1) { + System.out.println("GetHprof.onSuccess"); + if (client == arg1) { + try { + target = createTargetFile(); + BufferedOutputStream out = + new BufferedOutputStream(new FileOutputStream(target)); + out.write(arg0); + out.close(); + } catch (Exception e) { + e.printStackTrace(); + target = null; + } + wakeUp(); + } + } + } + + private int timeout; + + public Hprof(int timeout) { + this.timeout = timeout; + } + + @Override + public Map<String, String> getClassData(Client client) { + File hprofLocalFile = Hprof.doHprof(client, timeout); + if (hprofLocalFile == null) { + throw new RuntimeException("Failed getting dump..."); + } + System.out.println("Dump file is " + hprofLocalFile); + + try { + return analyzeHprof(hprofLocalFile); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java new file mode 100644 index 000000000000..dbd4c89b27cf --- /dev/null +++ b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/JDWPClassDataRetriever.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.classdataretrieval.jdwp; + +import com.android.ddmlib.Client; +import com.android.preload.classdataretrieval.ClassDataRetriever; + +import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket; +import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands; +import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants; +import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket; +import org.apache.harmony.jpda.tests.jdwp.share.JDWPTestCase; +import org.apache.harmony.jpda.tests.jdwp.share.JDWPUnitDebuggeeWrapper; +import org.apache.harmony.jpda.tests.share.JPDALogWriter; +import org.apache.harmony.jpda.tests.share.JPDATestOptions; + +import java.util.HashMap; +import java.util.Map; + +public class JDWPClassDataRetriever extends JDWPTestCase implements ClassDataRetriever { + + private final Client client; + + public JDWPClassDataRetriever() { + this(null); + } + + public JDWPClassDataRetriever(Client client) { + this.client = client; + } + + + @Override + protected String getDebuggeeClassName() { + return "<unset>"; + } + + @Override + public Map<String, String> getClassData(Client client) { + return new JDWPClassDataRetriever(client).retrieve(); + } + + private Map<String, String> retrieve() { + if (client == null) { + throw new IllegalStateException(); + } + + settings = createTestOptions("localhost:" + String.valueOf(client.getDebuggerListenPort())); + settings.setDebuggeeSuspend("n"); + + logWriter = new JPDALogWriter(System.out, "", false); + + try { + internalSetUp(); + + return retrieveImpl(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + internalTearDown(); + } + } + + private Map<String, String> retrieveImpl() { + try { + // Suspend the app. + { + CommandPacket packet = new CommandPacket( + JDWPCommands.VirtualMachineCommandSet.CommandSetID, + JDWPCommands.VirtualMachineCommandSet.SuspendCommand); + ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet); + if (reply.getErrorCode() != JDWPConstants.Error.NONE) { + return null; + } + } + + // List all classes. + CommandPacket packet = new CommandPacket( + JDWPCommands.VirtualMachineCommandSet.CommandSetID, + JDWPCommands.VirtualMachineCommandSet.AllClassesCommand); + ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet); + + if (reply.getErrorCode() != JDWPConstants.Error.NONE) { + return null; + } + + int classCount = reply.getNextValueAsInt(); + System.out.println("Runtime reported " + classCount + " classes."); + + Map<Long, String> classes = new HashMap<Long, String>(); + Map<Long, String> arrayClasses = new HashMap<Long, String>(); + + for (int i = 0; i < classCount; i++) { + byte refTypeTag = reply.getNextValueAsByte(); + long typeID = reply.getNextValueAsReferenceTypeID(); + String signature = reply.getNextValueAsString(); + /* int status = */ reply.getNextValueAsInt(); + + switch (refTypeTag) { + case JDWPConstants.TypeTag.CLASS: + case JDWPConstants.TypeTag.INTERFACE: + classes.put(typeID, signature); + break; + + case JDWPConstants.TypeTag.ARRAY: + arrayClasses.put(typeID, signature); + break; + } + } + + Map<String, String> result = new HashMap<String, String>(); + + // Parse all classes. + for (Map.Entry<Long, String> entry : classes.entrySet()) { + long typeID = entry.getKey(); + String signature = entry.getValue(); + + if (!checkClass(typeID, signature, result)) { + System.err.println("Issue investigating " + signature); + } + } + + // For arrays, look at the leaf component type. + for (Map.Entry<Long, String> entry : arrayClasses.entrySet()) { + long typeID = entry.getKey(); + String signature = entry.getValue(); + + if (!checkArrayClass(typeID, signature, result)) { + System.err.println("Issue investigating " + signature); + } + } + + return result; + } finally { + // Resume the app. + { + CommandPacket packet = new CommandPacket( + JDWPCommands.VirtualMachineCommandSet.CommandSetID, + JDWPCommands.VirtualMachineCommandSet.ResumeCommand); + /* ReplyPacket reply = */ debuggeeWrapper.vmMirror.performCommand(packet); + } + } + } + + private boolean checkClass(long typeID, String signature, Map<String, String> result) { + CommandPacket packet = new CommandPacket( + JDWPCommands.ReferenceTypeCommandSet.CommandSetID, + JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand); + packet.setNextValueAsReferenceTypeID(typeID); + ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet); + if (reply.getErrorCode() != JDWPConstants.Error.NONE) { + return false; + } + + long classLoaderID = reply.getNextValueAsObjectID(); + + // TODO: Investigate the classloader to have a better string? + String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID); + + result.put(getClassName(signature), classLoaderString); + + return true; + } + + private boolean checkArrayClass(long typeID, String signature, Map<String, String> result) { + // Classloaders of array classes are the same as the component class'. + CommandPacket packet = new CommandPacket( + JDWPCommands.ReferenceTypeCommandSet.CommandSetID, + JDWPCommands.ReferenceTypeCommandSet.ClassLoaderCommand); + packet.setNextValueAsReferenceTypeID(typeID); + ReplyPacket reply = debuggeeWrapper.vmMirror.performCommand(packet); + if (reply.getErrorCode() != JDWPConstants.Error.NONE) { + return false; + } + + long classLoaderID = reply.getNextValueAsObjectID(); + + // TODO: Investigate the classloader to have a better string? + String classLoaderString = (classLoaderID == 0) ? null : String.valueOf(classLoaderID); + + // For array classes, we *need* the signature directly. + result.put(signature, classLoaderString); + + return true; + } + + private static String getClassName(String signature) { + String withoutLAndSemicolon = signature.substring(1, signature.length() - 1); + return withoutLAndSemicolon.replace('/', '.'); + } + + + private static JPDATestOptions createTestOptions(String address) { + JPDATestOptions options = new JPDATestOptions(); + options.setAttachConnectorKind(); + options.setTimeout(1000); + options.setWaitingTime(1000); + options.setTransportAddress(address); + return options; + } + + @Override + protected JDWPUnitDebuggeeWrapper createDebuggeeWrapper() { + return new PreloadDebugeeWrapper(settings, logWriter); + } +} diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java new file mode 100644 index 000000000000..b9df6d0aeb93 --- /dev/null +++ b/tools/preload2/src/com/android/preload/classdataretrieval/jdwp/PreloadDebugeeWrapper.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.classdataretrieval.jdwp; + +import org.apache.harmony.jpda.tests.framework.LogWriter; +import org.apache.harmony.jpda.tests.jdwp.share.JDWPManualDebuggeeWrapper; +import org.apache.harmony.jpda.tests.share.JPDATestOptions; + +import java.io.IOException; + +public class PreloadDebugeeWrapper extends JDWPManualDebuggeeWrapper { + + public PreloadDebugeeWrapper(JPDATestOptions options, LogWriter writer) { + super(options, writer); + } + + @Override + protected Process launchProcess(String cmdLine) throws IOException { + return null; + } + + @Override + protected void WaitForProcessExit(Process process) { + } + +} diff --git a/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java new file mode 100644 index 000000000000..f45aad06ac6b --- /dev/null +++ b/tools/preload2/src/com/android/preload/ui/NullProgressMonitor.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.ui; + +import com.android.ddmlib.SyncService.ISyncProgressMonitor; + +public class NullProgressMonitor implements ISyncProgressMonitor { + + @Override + public void advance(int arg0) {} + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public void start(int arg0) {} + + @Override + public void startSubTask(String arg0) {} + + @Override + public void stop() {} +}
\ No newline at end of file diff --git a/tools/preload2/src/com/android/preload/ui/UI.java b/tools/preload2/src/com/android/preload/ui/UI.java new file mode 100644 index 000000000000..47174ddd0e07 --- /dev/null +++ b/tools/preload2/src/com/android/preload/ui/UI.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2015 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. + */ + +package com.android.preload.ui; + +import com.android.ddmlib.Client; +import com.android.ddmlib.ClientData; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.io.File; +import java.util.List; + +import javax.swing.Action; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JToolBar; +import javax.swing.ListModel; +import javax.swing.SwingUtilities; +import javax.swing.table.TableModel; + +public class UI extends JFrame { + + private JList<Client> clientList; + private JTable dataTable; + + // Shared file chooser, means the directory is retained. + private JFileChooser jfc; + + public UI(ListModel<Client> clientListModel, + TableModel dataTableModel, + List<Action> actions) { + super("Preloaded-classes computation"); + + getContentPane().add(new JScrollPane(clientList = new JList<Client>(clientListModel)), + BorderLayout.WEST); + clientList.setCellRenderer(new ClientListCellRenderer()); + // clientList.addListSelectionListener(listener); + + dataTable = new JTable(dataTableModel); + getContentPane().add(new JScrollPane(dataTable), BorderLayout.CENTER); + + JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL); + for (Action a : actions) { + if (a == null) { + toolbar.addSeparator(); + } else { + toolbar.add(a); + } + } + getContentPane().add(toolbar, BorderLayout.PAGE_START); + + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setBounds(100, 100, 800, 600); + } + + public Client getSelectedClient() { + return clientList.getSelectedValue(); + } + + public int getSelectedDataTableRow() { + return dataTable.getSelectedRow(); + } + + private JDialog currentWaitDialog = null; + + public void showWaitDialog() { + if (currentWaitDialog == null) { + currentWaitDialog = new JDialog(this, "Please wait...", true); + currentWaitDialog.getContentPane().add(new JLabel("Please be patient."), + BorderLayout.CENTER); + JProgressBar progress = new JProgressBar(JProgressBar.HORIZONTAL); + progress.setIndeterminate(true); + currentWaitDialog.getContentPane().add(progress, BorderLayout.SOUTH); + currentWaitDialog.setSize(200, 100); + currentWaitDialog.setLocationRelativeTo(null); + showWaitDialogLater(); + } + } + + private void showWaitDialogLater() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(true); // This is blocking. + } + } + }); + } + + public void updateWaitDialog(String s) { + if (currentWaitDialog != null) { + ((JLabel) currentWaitDialog.getContentPane().getComponent(0)).setText(s); + Dimension prefSize = currentWaitDialog.getPreferredSize(); + Dimension curSize = currentWaitDialog.getSize(); + if (prefSize.width > curSize.width || prefSize.height > curSize.height) { + currentWaitDialog.setSize(Math.max(prefSize.width, curSize.width), + Math.max(prefSize.height, curSize.height)); + currentWaitDialog.invalidate(); + } + } + } + + public void hideWaitDialog() { + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + currentWaitDialog = null; + } + } + + public void showMessageDialog(String s) { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try { + JOptionPane.showMessageDialog(this, s); + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + public boolean showConfirmDialog(String title, String message) { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try { + return JOptionPane.showConfirmDialog(this, title, message, JOptionPane.YES_NO_OPTION) + == JOptionPane.YES_OPTION; + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + public String showInputDialog(String message) { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try { + return JOptionPane.showInputDialog(message); + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + @SuppressWarnings("unchecked") + public <T> T showChoiceDialog(String title, String message, T[] choices) { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try{ + return (T)JOptionPane.showInputDialog(this, + title, + message, + JOptionPane.QUESTION_MESSAGE, + null, + choices, + choices[0]); + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + public File showSaveDialog() { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try{ + if (jfc == null) { + jfc = new JFileChooser(); + } + + int ret = jfc.showSaveDialog(this); + if (ret == JFileChooser.APPROVE_OPTION) { + return jfc.getSelectedFile(); + } else { + return null; + } + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + public File[] showOpenDialog(boolean multi) { + // Hide the wait dialog... + if (currentWaitDialog != null) { + currentWaitDialog.setVisible(false); + } + + try{ + if (jfc == null) { + jfc = new JFileChooser(); + } + + jfc.setMultiSelectionEnabled(multi); + int ret = jfc.showOpenDialog(this); + if (ret == JFileChooser.APPROVE_OPTION) { + return jfc.getSelectedFiles(); + } else { + return null; + } + } finally { + // And reshow it afterwards... + if (currentWaitDialog != null) { + showWaitDialogLater(); + } + } + } + + private class ClientListCellRenderer extends DefaultListCellRenderer { + + @Override + public Component getListCellRendererComponent(JList<?> list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + ClientData cd = ((Client) value).getClientData(); + String s = cd.getClientDescription() + " (pid " + cd.getPid() + ")"; + return super.getListCellRendererComponent(list, s, index, isSelected, cellHasFocus); + } + } +} |