summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java26
-rw-r--r--core/java/android/app/ActivityManagerNative.java37
-rw-r--r--core/java/android/app/ActivityThread.java37
-rw-r--r--core/java/android/app/ApplicationThreadNative.java28
-rw-r--r--core/java/android/app/IActivityManager.java7
-rw-r--r--core/java/android/app/IApplicationThread.java3
-rw-r--r--core/java/android/os/Debug.java19
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java64
8 files changed, 215 insertions, 6 deletions
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 301883f..fb60fdf 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -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" +
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 1fe85e6..43a08b5 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -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;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d788be8..53883b1 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -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) {
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 1c20062..dc2145f 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -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();
+ }
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 20c9a80..8ea59a7 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -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;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index c8ef17f..039bcb9 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -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;
}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 2e14667..d6b6d72 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -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() {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 93122c4..58aab08 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -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) { }