Merge "Allow "am" to initiate heap dumps."
This commit is contained in:
committed by
Android (Google) Code Review
commit
09d7d7dbc3
@@ -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" +
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
Reference in New Issue
Block a user