Merge "Frameworks/base: New preload tool"

am: f2c7983de1

* commit 'f2c7983de141d008c7c02e3331fbde67e13ac46c':
  Frameworks/base: New preload tool
This commit is contained in:
Andreas Gampe
2016-01-08 17:44:54 +00:00
committed by android-build-merger
28 changed files with 3056 additions and 0 deletions

32
tools/preload2/Android.mk Normal file
View File

@@ -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 $@

View File

@@ -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

View File

@@ -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();
}
}
}
}
}

View File

@@ -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) {
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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) {
}
}
}
}

View File

@@ -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);
}

View File

@@ -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());
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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() + ")";
}
}
}

View File

@@ -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');
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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) {
}
}

View File

@@ -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() {}
}

View File

@@ -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);
}
}
}