Merge "Allow "am" to initiate heap dumps."

This commit is contained in:
Andy McFadden
2010-07-13 12:19:52 -07:00
committed by Android (Google) Code Review
8 changed files with 215 additions and 6 deletions

View File

@@ -98,6 +98,8 @@ public class Am {
sendBroadcast();
} else if (op.equals("profile")) {
runProfile();
} else if (op.equals("dumpheap")) {
runDumpHeap();
} else {
throw new IllegalArgumentException("Unknown command: " + op);
}
@@ -424,6 +426,28 @@ public class Am {
}
}
private void runDumpHeap() throws Exception {
boolean managed = !"-n".equals(nextOption());
String process = nextArgRequired();
String heapFile = nextArgRequired();
ParcelFileDescriptor fd = null;
try {
fd = ParcelFileDescriptor.open(
new File(heapFile),
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE |
ParcelFileDescriptor.MODE_READ_WRITE);
} catch (FileNotFoundException e) {
System.err.println("Error: Unable to open file: " + heapFile);
return;
}
if (!mAm.dumpHeap(process, managed, heapFile, fd)) {
throw new AndroidException("HEAP DUMP FAILED on process " + process);
}
}
private class IntentReceiver extends IIntentReceiver.Stub {
private boolean mFinished = false;
@@ -593,6 +617,8 @@ public class Am {
"\n" +
" start profiling: am profile <PROCESS> start <FILE>\n" +
" stop profiling: am profile <PROCESS> stop\n" +
" dump heap: am dumpheap [flags] <PROCESS> <FILE>\n" +
" -n: dump native heap instead of managed heap\n" +
"\n" +
" <INTENT> specifications include these flags:\n" +
" [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +

View File

@@ -1294,6 +1294,19 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
case DUMP_HEAP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String process = data.readString();
boolean managed = data.readInt() != 0;
String path = data.readString();
ParcelFileDescriptor fd = data.readInt() != 0
? data.readFileDescriptor() : null;
boolean res = dumpHeap(process, managed, path, fd);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
}
}
return super.onTransact(code, data, reply, flags);
@@ -2874,6 +2887,28 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
public boolean dumpHeap(String process, boolean managed,
String path, ParcelFileDescriptor fd) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(process);
data.writeInt(managed ? 1 : 0);
data.writeString(path);
if (fd != null) {
data.writeInt(1);
fd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
data.writeInt(0);
}
mRemote.transact(DUMP_HEAP_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
reply.recycle();
data.recycle();
return res;
}
private IBinder mRemote;
}

View File

@@ -356,6 +356,11 @@ public final class ActivityThread {
ParcelFileDescriptor fd;
}
private static final class DumpHeapData {
String path;
ParcelFileDescriptor fd;
}
private final class ApplicationThread extends ApplicationThreadNative {
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%17s %8d";
@@ -623,6 +628,13 @@ public final class ActivityThread {
queueOrSendMessage(H.PROFILER_CONTROL, pcd, start ? 1 : 0);
}
public void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd) {
DumpHeapData dhd = new DumpHeapData();
dhd.path = path;
dhd.fd = fd;
queueOrSendMessage(H.DUMP_HEAP, dhd, managed ? 1 : 0);
}
public void setSchedulingGroup(int group) {
// Note: do this immediately, since going into the foreground
// should happen regardless of what pending work we have to do
@@ -874,6 +886,7 @@ public final class ActivityThread {
public static final int ENABLE_JIT = 132;
public static final int DISPATCH_PACKAGE_BROADCAST = 133;
public static final int SCHEDULE_CRASH = 134;
public static final int DUMP_HEAP = 135;
String codeToString(int code) {
if (localLOGV) {
switch (code) {
@@ -912,6 +925,7 @@ public final class ActivityThread {
case ENABLE_JIT: return "ENABLE_JIT";
case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST";
case SCHEDULE_CRASH: return "SCHEDULE_CRASH";
case DUMP_HEAP: return "DUMP_HEAP";
}
}
return "(unknown)";
@@ -1037,6 +1051,9 @@ public final class ActivityThread {
break;
case SCHEDULE_CRASH:
throw new RemoteServiceException((String)msg.obj);
case DUMP_HEAP:
handleDumpHeap(msg.arg1 != 0, (DumpHeapData)msg.obj);
break;
}
}
@@ -3015,6 +3032,26 @@ public final class ActivityThread {
}
}
final void handleDumpHeap(boolean managed, DumpHeapData dhd) {
if (managed) {
try {
Debug.dumpHprofData(dhd.path, dhd.fd.getFileDescriptor());
} catch (IOException e) {
Slog.w(TAG, "Managed heap dump failed on path " + dhd.path
+ " -- can the process access this path?");
} finally {
try {
dhd.fd.close();
} catch (IOException e) {
Slog.w(TAG, "Failure closing profile fd", e);
}
}
} else {
// TODO
Slog.w(TAG, "Native heap dump not yet implemented");
}
}
final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
boolean hasPkgInfo = false;
if (packages != null) {

View File

@@ -403,6 +403,17 @@ public abstract class ApplicationThreadNative extends Binder
scheduleCrash(msg);
return true;
}
case DUMP_HEAP_TRANSACTION:
{
data.enforceInterface(IApplicationThread.descriptor);
boolean managed = data.readInt() != 0;
String path = data.readString();
ParcelFileDescriptor fd = data.readInt() != 0
? data.readFileDescriptor() : null;
dumpHeap(managed, path, fd);
return true;
}
}
return super.onTransact(code, data, reply, flags);
@@ -829,5 +840,22 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
public void dumpHeap(boolean managed, String path,
ParcelFileDescriptor fd) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeInt(managed ? 1 : 0);
data.writeString(path);
if (fd != null) {
data.writeInt(1);
fd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
data.writeInt(0);
}
mRemote.transact(DUMP_HEAP_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
}

View File

@@ -316,7 +316,11 @@ public interface IActivityManager extends IInterface {
public void crashApplication(int uid, int initialPid, String packageName,
String message) throws RemoteException;
// Cause the specified process to dump the specified heap.
public boolean dumpHeap(String process, boolean managed, String path,
ParcelFileDescriptor fd) throws RemoteException;
/*
* Private non-Binder interfaces
*/
@@ -533,4 +537,5 @@ public interface IActivityManager extends IInterface {
int SET_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+111;
int IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+112;
int CRASH_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+113;
int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+114;
}

View File

@@ -97,6 +97,8 @@ public interface IApplicationThread extends IInterface {
void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException;
void profilerControl(boolean start, String path, ParcelFileDescriptor fd)
throws RemoteException;
void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd)
throws RemoteException;
void setSchedulingGroup(int group) throws RemoteException;
void getMemoryInfo(Debug.MemoryInfo outInfo) throws RemoteException;
static final int PACKAGE_REMOVED = 0;
@@ -140,4 +142,5 @@ public interface IApplicationThread extends IInterface {
int SCHEDULE_SUICIDE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32;
int DISPATCH_PACKAGE_BROADCAST_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33;
int SCHEDULE_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34;
int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35;
}

View File

@@ -730,7 +730,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
}
/**
* Dump "hprof" data to the specified file. This will cause a GC.
* Dump "hprof" data to the specified file. This may cause a GC.
*
* @param fileName Full pathname of output file (e.g. "/sdcard/dump.hprof").
* @throws UnsupportedOperationException if the VM was built without
@@ -742,11 +742,24 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
}
/**
* Collect "hprof" and send it to DDMS. This will cause a GC.
* Like dumpHprofData(String), but takes an already-opened
* FileDescriptor to which the trace is written. The file name is also
* supplied simply for logging. Makes a dup of the file descriptor.
*
* Primarily for use by the "am" shell command.
*
* @hide
*/
public static void dumpHprofData(String fileName, FileDescriptor fd)
throws IOException {
VMDebug.dumpHprofData(fileName, fd);
}
/**
* Collect "hprof" and send it to DDMS. This may cause a GC.
*
* @throws UnsupportedOperationException if the VM was built without
* HPROF support.
*
* @hide
*/
public static void dumpHprofDataDdms() {

View File

@@ -11562,7 +11562,69 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
}
}
}
public boolean dumpHeap(String process, boolean managed,
String path, ParcelFileDescriptor fd) throws RemoteException {
try {
synchronized (this) {
// note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
// its own permission (same as profileControl).
if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission "
+ android.Manifest.permission.SET_ACTIVITY_WATCHER);
}
if (fd == null) {
throw new IllegalArgumentException("null fd");
}
ProcessRecord proc = null;
try {
int pid = Integer.parseInt(process);
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(pid);
}
} catch (NumberFormatException e) {
}
if (proc == null) {
HashMap<String, SparseArray<ProcessRecord>> all
= mProcessNames.getMap();
SparseArray<ProcessRecord> procs = all.get(process);
if (procs != null && procs.size() > 0) {
proc = procs.valueAt(0);
}
}
if (proc == null || proc.thread == null) {
throw new IllegalArgumentException("Unknown process: " + process);
}
boolean isSecure = "1".equals(SystemProperties.get(SYSTEM_SECURE, "0"));
if (isSecure) {
if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
throw new SecurityException("Process not debuggable: " + proc);
}
}
proc.thread.dumpHeap(managed, path, fd);
fd = null;
return true;
}
} catch (RemoteException e) {
throw new IllegalStateException("Process disappeared");
} finally {
if (fd != null) {
try {
fd.close();
} catch (IOException e) {
}
}
}
}
/** In this method we try to acquire our lock to make sure that we have not deadlocked */
public void monitor() {
synchronized (this) { }