summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java22
-rw-r--r--core/java/android/app/ActivityManagerNative.java42
-rw-r--r--core/java/android/app/ActivityThread.java4
-rw-r--r--core/java/android/app/IActivityManager.java9
-rw-r--r--core/java/android/app/Notification.java1
-rw-r--r--core/java/android/util/DebugUtils.java80
-rw-r--r--core/java/com/android/internal/app/DumpHeapActivity.java106
-rw-r--r--core/java/com/android/internal/app/ProcessStats.java78
-rw-r--r--core/res/AndroidManifest.xml13
-rw-r--r--core/res/res/values/strings.xml17
-rwxr-xr-xcore/res/res/values/symbols.xml4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java216
-rw-r--r--services/core/java/com/android/server/am/DumpHeapProvider.java89
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java6
14 files changed, 619 insertions, 68 deletions
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index bc0c451..29ba1d7 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -117,6 +117,8 @@ public class Am extends BaseCommand {
" am dumpheap [--user <USER_ID> current] [-n] <PROCESS> <FILE>\n" +
" am set-debug-app [-w] [--persistent] <PACKAGE>\n" +
" am clear-debug-app\n" +
+ " am set-watch-heap <PROCESS> <MEM-LIMIT>\n" +
+ " am clear-watch-heap\n" +
" am monitor [--gdb <port>]\n" +
" am hang [--allow-restart]\n" +
" am restart\n" +
@@ -211,6 +213,11 @@ public class Am extends BaseCommand {
"\n" +
"am clear-debug-app: clear the previously set-debug-app.\n" +
"\n" +
+ "am set-watch-heap: start monitoring pss size of <PROCESS>, if it is at or\n" +
+ " above <HEAP-LIMIT> then a heap dump is collected for the user to report\n" +
+ "\n" +
+ "am clear-watch-heap: clear the previously set-watch-heap.\n" +
+ "\n" +
"am bug-report: request bug report generation; will launch UI\n" +
" when done to select where it should be delivered.\n" +
"\n" +
@@ -342,6 +349,10 @@ public class Am extends BaseCommand {
runSetDebugApp();
} else if (op.equals("clear-debug-app")) {
runClearDebugApp();
+ } else if (op.equals("set-watch-heap")) {
+ runSetWatchHeap();
+ } else if (op.equals("clear-watch-heap")) {
+ runClearWatchHeap();
} else if (op.equals("bug-report")) {
runBugReport();
} else if (op.equals("monitor")) {
@@ -1172,6 +1183,17 @@ public class Am extends BaseCommand {
mAm.setDebugApp(null, false, true);
}
+ private void runSetWatchHeap() throws Exception {
+ String proc = nextArgRequired();
+ String limit = nextArgRequired();
+ mAm.setDumpHeapDebugLimit(proc, Long.parseLong(limit));
+ }
+
+ private void runClearWatchHeap() throws Exception {
+ String proc = nextArgRequired();
+ mAm.setDumpHeapDebugLimit(proc, -1);
+ }
+
private void runBugReport() throws Exception {
mAm.requestBugReport();
System.out.println("Your lovely bug report is being created; please be patient.");
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 3197461..997f69d 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2423,6 +2423,23 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
+
+ case SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String procName = data.readString();
+ long maxMemSize = data.readLong();
+ setDumpHeapDebugLimit(procName, maxMemSize);
+ reply.writeNoException();
+ return true;
+ }
+
+ case DUMP_HEAP_FINISHED_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String path = data.readString();
+ dumpHeapFinished(path);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -5616,5 +5633,30 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
+ @Override
+ public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(processName);
+ data.writeLong(maxMemSize);
+ mRemote.transact(SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
+ public void dumpHeapFinished(String path) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(path);
+ mRemote.transact(DUMP_HEAP_FINISHED_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 653b951..c93cf22 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4247,6 +4247,10 @@ public final class ActivityThread {
} else {
Debug.dumpNativeHeap(dhd.fd.getFileDescriptor());
}
+ try {
+ ActivityManagerNative.getDefault().dumpHeapFinished(dhd.path);
+ } catch (RemoteException e) {
+ }
}
final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1277cfa..3dcbdd2 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -482,6 +482,9 @@ public interface IActivityManager extends IInterface {
public void systemBackupRestored() throws RemoteException;
public void notifyCleartextNetwork(int uid, byte[] firstPacket) throws RemoteException;
+ public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException;
+ public void dumpHeapFinished(String path) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -512,7 +515,7 @@ public interface IActivityManager extends IInterface {
dest.writeStrongBinder(null);
}
dest.writeStrongBinder(connection);
- dest.writeInt(noReleaseNeeded ? 1:0);
+ dest.writeInt(noReleaseNeeded ? 1 : 0);
}
public static final Parcelable.Creator<ContentProviderHolder> CREATOR
@@ -531,7 +534,7 @@ public interface IActivityManager extends IInterface {
private ContentProviderHolder(Parcel source) {
info = ProviderInfo.CREATOR.createFromParcel(source);
provider = ContentProviderNative.asInterface(
- source.readStrongBinder());
+ source.readStrongBinder());
connection = source.readStrongBinder();
noReleaseNeeded = source.readInt() != 0;
}
@@ -813,4 +816,6 @@ public interface IActivityManager extends IInterface {
int REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+284;
int RESIZE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+285;
int GET_LOCK_TASK_MODE_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+286;
+ int SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+287;
+ int DUMP_HEAP_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+288;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 593585f..85a6aff 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -417,7 +417,6 @@ public class Notification implements Parcelable
* Bit to be bitwise-ored into the {@link #flags} field that should be
* set if the notification should be canceled when it is clicked by the
* user.
-
*/
public static final int FLAG_AUTO_CANCEL = 0x00000010;
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index f607207..84d9ce8 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -16,6 +16,7 @@
package android.util;
+import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
@@ -123,4 +124,83 @@ public class DebugUtils {
}
}
+ /** @hide */
+ public static void printSizeValue(PrintWriter pw, long number) {
+ float result = number;
+ String suffix = "";
+ if (result > 900) {
+ suffix = "KB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "MB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "GB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "TB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "PB";
+ result = result / 1024;
+ }
+ String value;
+ if (result < 1) {
+ value = String.format("%.2f", result);
+ } else if (result < 10) {
+ value = String.format("%.1f", result);
+ } else if (result < 100) {
+ value = String.format("%.0f", result);
+ } else {
+ value = String.format("%.0f", result);
+ }
+ pw.print(value);
+ pw.print(suffix);
+ }
+
+ /** @hide */
+ public static String sizeValueToString(long number, StringBuilder outBuilder) {
+ if (outBuilder == null) {
+ outBuilder = new StringBuilder(32);
+ }
+ float result = number;
+ String suffix = "";
+ if (result > 900) {
+ suffix = "KB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "MB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "GB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "TB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "PB";
+ result = result / 1024;
+ }
+ String value;
+ if (result < 1) {
+ value = String.format("%.2f", result);
+ } else if (result < 10) {
+ value = String.format("%.1f", result);
+ } else if (result < 100) {
+ value = String.format("%.0f", result);
+ } else {
+ value = String.format("%.0f", result);
+ }
+ outBuilder.append(value);
+ outBuilder.append(suffix);
+ return outBuilder.toString();
+ }
}
diff --git a/core/java/com/android/internal/app/DumpHeapActivity.java b/core/java/com/android/internal/app/DumpHeapActivity.java
new file mode 100644
index 0000000..7e70b0c
--- /dev/null
+++ b/core/java/com/android/internal/app/DumpHeapActivity.java
@@ -0,0 +1,106 @@
+/*
+ * 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.internal.app;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ClipData;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.DebugUtils;
+
+/**
+ * This activity is displayed when the system has collected a heap dump from
+ * a large process and the user has selected to share it.
+ */
+public class DumpHeapActivity extends Activity {
+ /** The process we are reporting */
+ public static final String KEY_PROCESS = "process";
+ /** The size limit the process reached */
+ public static final String KEY_SIZE = "size";
+
+ // Broadcast action to determine when to delete the current dump heap data.
+ public static final String ACTION_DELETE_DUMPHEAP = "com.android.server.am.DELETE_DUMPHEAP";
+
+ // Extra for above: delay delete of data, since the user is in the process of sharing it.
+ public static final String EXTRA_DELAY_DELETE = "delay_delete";
+
+ static final public Uri JAVA_URI = Uri.parse("content://com.android.server.heapdump/java");
+
+ String mProcess;
+ long mSize;
+ AlertDialog mDialog;
+ boolean mHandled = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mProcess = getIntent().getStringExtra(KEY_PROCESS);
+ mSize = getIntent().getLongExtra(KEY_SIZE, 0);
+ AlertDialog.Builder b = new AlertDialog.Builder(this,
+ android.R.style.Theme_Material_Light_Dialog_Alert);
+ b.setTitle(com.android.internal.R.string.dump_heap_title);
+ b.setMessage(getString(com.android.internal.R.string.dump_heap_text,
+ mProcess, DebugUtils.sizeValueToString(mSize, null)));
+ b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mHandled = true;
+ sendBroadcast(new Intent(ACTION_DELETE_DUMPHEAP));
+ finish();
+ }
+ });
+ b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mHandled = true;
+ Intent broadcast = new Intent(ACTION_DELETE_DUMPHEAP);
+ broadcast.putExtra(EXTRA_DELAY_DELETE, true);
+ sendBroadcast(broadcast);
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ ClipData clip = ClipData.newUri(getContentResolver(), "Heap Dump", JAVA_URI);
+ intent.setClipData(clip);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setType(clip.getDescription().getMimeType(0));
+ intent.putExtra(Intent.EXTRA_STREAM, JAVA_URI);
+ startActivity(Intent.createChooser(intent,
+ getText(com.android.internal.R.string.dump_heap_title)));
+ finish();
+ }
+ });
+ mDialog = b.show();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (!isChangingConfigurations()) {
+ if (!mHandled) {
+ sendBroadcast(new Intent(ACTION_DELETE_DUMPHEAP));
+ }
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mDialog.dismiss();
+ }
+}
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 70fb510..75beee9 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -24,6 +24,7 @@ import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -897,17 +898,17 @@ public final class ProcessStats implements Parcelable {
pw.print(STATE_NAMES[procStates[ip]]); pw.print(": ");
pw.print(count);
pw.print(" samples ");
- printSizeValue(pw, proc.getPssMinimum(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssMinimum(bucket) * 1024);
pw.print(" ");
- printSizeValue(pw, proc.getPssAverage(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssAverage(bucket) * 1024);
pw.print(" ");
- printSizeValue(pw, proc.getPssMaximum(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssMaximum(bucket) * 1024);
pw.print(" / ");
- printSizeValue(pw, proc.getPssUssMinimum(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssUssMinimum(bucket) * 1024);
pw.print(" ");
- printSizeValue(pw, proc.getPssUssAverage(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssUssAverage(bucket) * 1024);
pw.print(" ");
- printSizeValue(pw, proc.getPssUssMaximum(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssUssMaximum(bucket) * 1024);
pw.println();
}
}
@@ -924,9 +925,9 @@ public final class ProcessStats implements Parcelable {
if (proc.mNumCachedKill != 0) {
pw.print(prefix); pw.print("Killed from cached state: ");
pw.print(proc.mNumCachedKill); pw.print(" times from pss ");
- printSizeValue(pw, proc.mMinCachedKillPss * 1024); pw.print("-");
- printSizeValue(pw, proc.mAvgCachedKillPss * 1024); pw.print("-");
- printSizeValue(pw, proc.mMaxCachedKillPss * 1024); pw.println();
+ DebugUtils.printSizeValue(pw, proc.mMinCachedKillPss * 1024); pw.print("-");
+ DebugUtils.printSizeValue(pw, proc.mAvgCachedKillPss * 1024); pw.print("-");
+ DebugUtils.printSizeValue(pw, proc.mMaxCachedKillPss * 1024); pw.println();
}
}
@@ -939,11 +940,11 @@ public final class ProcessStats implements Parcelable {
int bucket, int index) {
pw.print(prefix); pw.print(label);
pw.print(": ");
- printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
+ DebugUtils.printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
pw.print(" min, ");
- printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
+ DebugUtils.printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
pw.print(" avg, ");
- printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
+ DebugUtils.printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
pw.println(" max");
}
@@ -1150,43 +1151,6 @@ public final class ProcessStats implements Parcelable {
pw.print("%");
}
- static void printSizeValue(PrintWriter pw, long number) {
- float result = number;
- String suffix = "";
- if (result > 900) {
- suffix = "KB";
- result = result / 1024;
- }
- if (result > 900) {
- suffix = "MB";
- result = result / 1024;
- }
- if (result > 900) {
- suffix = "GB";
- result = result / 1024;
- }
- if (result > 900) {
- suffix = "TB";
- result = result / 1024;
- }
- if (result > 900) {
- suffix = "PB";
- result = result / 1024;
- }
- String value;
- if (result < 1) {
- value = String.format("%.2f", result);
- } else if (result < 10) {
- value = String.format("%.1f", result);
- } else if (result < 100) {
- value = String.format("%.0f", result);
- } else {
- value = String.format("%.0f", result);
- }
- pw.print(value);
- pw.print(suffix);
- }
-
public static void dumpProcessListCsv(PrintWriter pw, ArrayList<ProcessState> procs,
boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates,
boolean sepProcStates, int[] procStates, long now) {
@@ -2437,7 +2401,7 @@ public final class ProcessStats implements Parcelable {
pw.print(prefix);
pw.print(label);
pw.print(": ");
- printSizeValue(pw, mem);
+ DebugUtils.printSizeValue(pw, mem);
pw.print(" (");
pw.print(samples);
pw.print(" samples)");
@@ -2475,7 +2439,7 @@ public final class ProcessStats implements Parcelable {
totalPss = printMemoryCategory(pw, " ", "Z-Ram ", totalMem.sysMemZRamWeight,
totalMem.totalTime, totalPss, totalMem.sysMemSamples);
pw.print(" TOTAL : ");
- printSizeValue(pw, totalPss);
+ DebugUtils.printSizeValue(pw, totalPss);
pw.println();
printMemoryCategory(pw, " ", STATE_NAMES[STATE_SERVICE_RESTARTING],
totalMem.processStateWeight[STATE_SERVICE_RESTARTING], totalMem.totalTime, totalPss,
@@ -3781,17 +3745,17 @@ public final class ProcessStats implements Parcelable {
printPercent(pw, (double) totalTime / (double) overallTime);
if (numPss > 0) {
pw.print(" (");
- printSizeValue(pw, minPss * 1024);
+ DebugUtils.printSizeValue(pw, minPss * 1024);
pw.print("-");
- printSizeValue(pw, avgPss * 1024);
+ DebugUtils.printSizeValue(pw, avgPss * 1024);
pw.print("-");
- printSizeValue(pw, maxPss * 1024);
+ DebugUtils.printSizeValue(pw, maxPss * 1024);
pw.print("/");
- printSizeValue(pw, minUss * 1024);
+ DebugUtils.printSizeValue(pw, minUss * 1024);
pw.print("-");
- printSizeValue(pw, avgUss * 1024);
+ DebugUtils.printSizeValue(pw, avgUss * 1024);
pw.print("-");
- printSizeValue(pw, maxUss * 1024);
+ DebugUtils.printSizeValue(pw, maxUss * 1024);
if (full) {
pw.print(" over ");
pw.print(numPss);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2ce5bb3..ed4776b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3033,6 +3033,19 @@
android:excludeFromRecents="true"
android:process=":ui">
</activity>
+ <activity android:name="com.android.internal.app.DumpHeapActivity"
+ android:theme="@style/Theme.Translucent.NoTitleBar"
+ android:label="@string/dump_heap_title"
+ android:finishOnCloseSystemDialogs="true"
+ android:noHistory="true"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+ <provider android:name="com.android.server.am.DumpHeapProvider"
+ android:authorities="com.android.server.heapdump"
+ android:grantUriPermissions="true"
+ android:multiprocess="false"
+ android:singleUser="true" />
<activity android:name="android.accounts.ChooseAccountActivity"
android:excludeFromRecents="true"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 67ce159..bf370f4 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3715,6 +3715,23 @@
<string name="new_app_action">Start <xliff:g id="old_app">%1$s</xliff:g></string>
<string name="new_app_description">Stop the old app without saving.</string>
+ <!-- Notification text to tell the user that a process has exceeded its memory limit. -->
+ <string name="dump_heap_notification"><xliff:g id="proc">%1$s</xliff:g> exceeded memory
+ limit</string>
+
+ <!-- Notification details to tell the user that a process has exceeded its memory limit. -->
+ <string name="dump_heap_notification_detail">Heap dump has been collected;
+ touch to share</string>
+
+ <!-- Title of dialog prompting the user to share a heap dump. -->
+ <string name="dump_heap_title">Share heap dump?</string>
+
+ <!-- Text of dialog prompting the user to share a heap dump. -->
+ <string name="dump_heap_text">The process <xliff:g id="proc">%1$s</xliff:g> has exceeded
+ its process memory limit of <xliff:g id="size">%2$s</xliff:g>. A heap dump is available
+ for you to share with its developer. Be careful: this heap dump can contain any
+ of your personal information that the application has access to.</string>
+
<!-- Displayed in the title of the chooser for things to do with text that
is to be sent to another application. For example, I can send
text through SMS or IM. A dialog with those choices would be shown,
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 078c12f..b204a0b 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1723,6 +1723,10 @@
<java-symbol type="string" name="data_usage_wifi_limit_title" />
<java-symbol type="string" name="default_wallpaper_component" />
<java-symbol type="string" name="dlg_ok" />
+ <java-symbol type="string" name="dump_heap_notification" />
+ <java-symbol type="string" name="dump_heap_notification_detail" />
+ <java-symbol type="string" name="dump_heap_text" />
+ <java-symbol type="string" name="dump_heap_title" />
<java-symbol type="string" name="factorytest_failed" />
<java-symbol type="string" name="factorytest_no_action" />
<java-symbol type="string" name="factorytest_not_system" />
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1247105..d8a9d3e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -56,11 +56,13 @@ import android.os.storage.StorageManager;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.SparseIntArray;
import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.DumpHeapActivity;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ProcessMap;
@@ -1126,6 +1128,11 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean mAutoStopProfiler = false;
int mProfileType = 0;
String mOpenGlTraceApp = null;
+ final ArrayMap<String, Long> mMemWatchProcesses = new ArrayMap<>();
+ String mMemWatchDumpProcName;
+ String mMemWatchDumpFile;
+ int mMemWatchDumpPid;
+ int mMemWatchDumpUid;
final long[] mTmpLong = new long[1];
@@ -1268,6 +1275,8 @@ public final class ActivityManagerService extends ActivityManagerNative
static final int DISMISS_DIALOG_MSG = 48;
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG = 49;
static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 50;
+ static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 51;
+ static final int DELETE_DUMPHEAP_MSG = 52;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1822,6 +1831,78 @@ public final class ActivityManagerService extends ActivityManagerNative
}
break;
}
+ case POST_DUMP_HEAP_NOTIFICATION_MSG: {
+ final String procName;
+ final int uid;
+ final long memLimit;
+ synchronized (ActivityManagerService.this) {
+ procName = mMemWatchDumpProcName;
+ uid = mMemWatchDumpUid;
+ Long limit = mMemWatchProcesses.get(procName);
+ memLimit = limit != null ? limit : 0;
+ }
+ if (procName == null) {
+ return;
+ }
+
+ if (DEBUG_PSS) Slog.d(TAG, "Showing dump heap notification from "
+ + procName + "/" + uid);
+
+ INotificationManager inm = NotificationManager.getService();
+ if (inm == null) {
+ return;
+ }
+
+ String text = mContext.getString(R.string.dump_heap_notification, procName);
+ Notification notification = new Notification();
+ notification.icon = com.android.internal.R.drawable.stat_sys_adb;
+ notification.when = 0;
+ notification.flags = Notification.FLAG_ONGOING_EVENT|Notification.FLAG_AUTO_CANCEL;
+ notification.tickerText = text;
+ notification.defaults = 0; // please be quiet
+ notification.sound = null;
+ notification.vibrate = null;
+ notification.color = mContext.getResources().getColor(
+ com.android.internal.R.color.system_notification_accent_color);
+ Intent deleteIntent = new Intent();
+ deleteIntent.setAction(DumpHeapActivity.ACTION_DELETE_DUMPHEAP);
+ notification.deleteIntent = PendingIntent.getBroadcastAsUser(mContext, 0,
+ deleteIntent, 0, UserHandle.OWNER);
+ Intent intent = new Intent();
+ intent.setClassName("android", DumpHeapActivity.class.getName());
+ intent.putExtra(DumpHeapActivity.KEY_PROCESS, procName);
+ intent.putExtra(DumpHeapActivity.KEY_SIZE, memLimit);
+ int userId = UserHandle.getUserId(uid);
+ notification.setLatestEventInfo(mContext, text,
+ mContext.getText(R.string.dump_heap_notification_detail),
+ PendingIntent.getActivityAsUser(mContext, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null,
+ new UserHandle(userId)));
+
+ try {
+ int[] outId = new int[1];
+ inm.enqueueNotificationWithTag("android", "android", null,
+ R.string.dump_heap_notification,
+ notification, outId, userId);
+ } catch (RuntimeException e) {
+ Slog.w(ActivityManagerService.TAG,
+ "Error showing notification for dump heap", e);
+ } catch (RemoteException e) {
+ }
+ } break;
+ case DELETE_DUMPHEAP_MSG: {
+ revokeUriPermission(ActivityThread.currentActivityThread().getApplicationThread(),
+ DumpHeapActivity.JAVA_URI,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ UserHandle.myUserId());
+ synchronized (ActivityManagerService.this) {
+ mMemWatchDumpFile = null;
+ mMemWatchDumpProcName = null;
+ mMemWatchDumpPid = -1;
+ mMemWatchDumpUid = -1;
+ }
+ } break;
}
}
};
@@ -1908,7 +1989,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (pss != 0 && proc.thread != null && proc.setProcState == procState
&& proc.pid == pid && proc.lastPssTime == lastPssTime) {
num++;
- recordPssSample(proc, procState, pss, tmp[0],
+ recordPssSampleLocked(proc, procState, pss, tmp[0],
SystemClock.uptimeMillis());
}
}
@@ -5706,7 +5787,7 @@ public final class ActivityManagerService extends ActivityManagerNative
for (String pkg : pkgs) {
synchronized (ActivityManagerService.this) {
if (forceStopPackageLocked(pkg, -1, false, false, false, false, false,
- 0, "finished booting")) {
+ 0, "query restart")) {
setResultCode(Activity.RESULT_OK);
return;
}
@@ -5716,6 +5797,19 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}, pkgFilter);
+ IntentFilter dumpheapFilter = new IntentFilter();
+ dumpheapFilter.addAction(DumpHeapActivity.ACTION_DELETE_DUMPHEAP);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getBooleanExtra(DumpHeapActivity.EXTRA_DELAY_DELETE, false)) {
+ mHandler.sendEmptyMessageDelayed(POST_DUMP_HEAP_NOTIFICATION_MSG, 5*60*1000);
+ } else {
+ mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG);
+ }
+ }
+ }, dumpheapFilter);
+
// Let system services know.
mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -12741,6 +12835,22 @@ public final class ActivityManagerService extends ActivityManagerNative
+ " mOrigWaitForDebugger=" + mOrigWaitForDebugger);
}
}
+ if (mMemWatchProcesses.size() > 0) {
+ pw.println(" Mem watch processes:");
+ for (int i=0; i<mMemWatchProcesses.size(); i++) {
+ if (needSep) {
+ pw.println();
+ needSep = false;
+ }
+ pw.print(" "); pw.print(mMemWatchProcesses.keyAt(i));
+ pw.print(": "); DebugUtils.printSizeValue(pw, mMemWatchProcesses.valueAt(i));
+ pw.println();
+ }
+ pw.print(" mMemWatchDumpProcName="); pw.println(mMemWatchDumpProcName);
+ pw.print(" mMemWatchDumpFile="); pw.println(mMemWatchDumpFile);
+ pw.print(" mMemWatchDumpPid="); pw.print(mMemWatchDumpPid);
+ pw.print(" mMemWatchDumpUid="); pw.println(mMemWatchDumpUid);
+ }
if (mOpenGlTraceApp != null) {
if (dumpPackage == null || dumpPackage.equals(mOpenGlTraceApp)) {
if (needSep) {
@@ -13423,8 +13533,9 @@ public final class ActivityManagerService extends ActivityManagerNative
pw.print(" ");
pw.print("state: cur="); pw.print(ProcessList.makeProcStateString(r.curProcState));
pw.print(" set="); pw.print(ProcessList.makeProcStateString(r.setProcState));
- pw.print(" lastPss="); pw.print(r.lastPss);
- pw.print(" lastCachedPss="); pw.println(r.lastCachedPss);
+ pw.print(" lastPss="); DebugUtils.printSizeValue(pw, r.lastPss*1024);
+ pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, r.lastCachedPss*1024);
+ pw.println();
pw.print(prefix);
pw.print(" ");
pw.print("cached="); pw.print(r.cached);
@@ -17232,7 +17343,7 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* Record new PSS sample for a process.
*/
- void recordPssSample(ProcessRecord proc, int procState, long pss, long uss, long now) {
+ void recordPssSampleLocked(ProcessRecord proc, int procState, long pss, long uss, long now) {
EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss*1024, uss*1024);
proc.lastPssTime = now;
proc.baseProcessTracker.addPss(pss, uss, true, proc.pkgList);
@@ -17246,6 +17357,67 @@ public final class ActivityManagerService extends ActivityManagerNative
if (procState >= ActivityManager.PROCESS_STATE_HOME) {
proc.lastCachedPss = pss;
}
+
+ Long check = mMemWatchProcesses.get(proc.processName);
+ if (check != null) {
+ if ((pss*1024) >= check && proc.thread != null && mMemWatchDumpProcName == null) {
+ boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+ if (!isDebuggable) {
+ if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ isDebuggable = true;
+ }
+ }
+ if (isDebuggable) {
+ Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting");
+ final ProcessRecord myProc = proc;
+ final File heapdumpFile = DumpHeapProvider.getJavaFile();
+ mMemWatchDumpProcName = proc.processName;
+ mMemWatchDumpFile = heapdumpFile.toString();
+ mMemWatchDumpPid = proc.pid;
+ mMemWatchDumpUid = proc.uid;
+ BackgroundThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ revokeUriPermission(ActivityThread.currentActivityThread()
+ .getApplicationThread(),
+ DumpHeapActivity.JAVA_URI,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ UserHandle.myUserId());
+ ParcelFileDescriptor fd = null;
+ try {
+ heapdumpFile.delete();
+ fd = ParcelFileDescriptor.open(heapdumpFile,
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE |
+ ParcelFileDescriptor.MODE_READ_WRITE);
+ IApplicationThread thread = myProc.thread;
+ if (thread != null) {
+ try {
+ if (DEBUG_PSS) Slog.d(TAG, "Requesting dump heap from "
+ + myProc + " to " + heapdumpFile);
+ thread.dumpHeap(true, heapdumpFile.toString(), fd);
+ } catch (RemoteException e) {
+ }
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } finally {
+ if (fd != null) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ });
+ } else {
+ Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check
+ + ", but debugging not enabled");
+ }
+ }
+ }
}
/**
@@ -17604,7 +17776,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// states, which well tend to give noisy data.
long start = SystemClock.uptimeMillis();
long pss = Debug.getPss(app.pid, mTmpLong, null);
- recordPssSample(app, app.curProcState, pss, mTmpLong[0], now);
+ recordPssSampleLocked(app, app.curProcState, pss, mTmpLong[0], now);
mPendingPssProcesses.remove(app);
Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState
+ " to " + app.curProcState + ": "
@@ -18424,6 +18596,38 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ @Override
+ public void setDumpHeapDebugLimit(String processName, long maxMemSize) {
+ enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP,
+ "setDumpHeapDebugLimit()");
+ synchronized (this) {
+ if (maxMemSize > 0) {
+ mMemWatchProcesses.put(processName, maxMemSize);
+ mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG);
+ } else {
+ mMemWatchProcesses.remove(processName);
+ }
+ }
+ }
+
+ @Override
+ public void dumpHeapFinished(String path) {
+ synchronized (this) {
+ if (Binder.getCallingPid() != mMemWatchDumpPid) {
+ Slog.w(TAG, "dumpHeapFinished: Calling pid " + Binder.getCallingPid()
+ + " does not match last pid " + mMemWatchDumpPid);
+ return;
+ }
+ if (mMemWatchDumpFile == null || !mMemWatchDumpFile.equals(path)) {
+ Slog.w(TAG, "dumpHeapFinished: Calling path " + path
+ + " does not match last path " + mMemWatchDumpFile);
+ return;
+ }
+ if (DEBUG_PSS) Slog.d(TAG, "Dump heap finished for " + path);
+ mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG);
+ }
+ }
+
/** In this method we try to acquire our lock to make sure that we have not deadlocked */
public void monitor() {
synchronized (this) { }
diff --git a/services/core/java/com/android/server/am/DumpHeapProvider.java b/services/core/java/com/android/server/am/DumpHeapProvider.java
new file mode 100644
index 0000000..a8b639e
--- /dev/null
+++ b/services/core/java/com/android/server/am/DumpHeapProvider.java
@@ -0,0 +1,89 @@
+/*
+ * 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.server.am;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+public class DumpHeapProvider extends ContentProvider {
+ static final Object sLock = new Object();
+ static File sHeapDumpJavaFile;
+
+ static public File getJavaFile() {
+ synchronized (sLock) {
+ return sHeapDumpJavaFile;
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ synchronized (sLock) {
+ File dataDir = Environment.getDataDirectory();
+ File systemDir = new File(dataDir, "system");
+ File heapdumpDir = new File(systemDir, "heapdump");
+ heapdumpDir.mkdir();
+ sHeapDumpJavaFile = new File(heapdumpDir, "javaheap.bin");
+ }
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return "application/octet-stream";
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ synchronized (sLock) {
+ String path = uri.getEncodedPath();
+ final String tag = Uri.decode(path);
+ if (tag.equals("/java")) {
+ return ParcelFileDescriptor.open(sHeapDumpJavaFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ } else {
+ throw new FileNotFoundException("Invalid path for " + uri);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a6c616a..f374c86 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.EventLog;
import android.util.Slog;
import com.android.internal.app.ProcessStats;
@@ -248,8 +249,9 @@ final class ProcessRecord {
pw.println();
pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
pw.print(" lruSeq="); pw.print(lruSeq);
- pw.print(" lastPss="); pw.print(lastPss);
- pw.print(" lastCachedPss="); pw.println(lastCachedPss);
+ pw.print(" lastPss="); DebugUtils.printSizeValue(pw, lastPss*1024);
+ pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, lastCachedPss*1024);
+ pw.println();
pw.print(prefix); pw.print("cached="); pw.print(cached);
pw.print(" empty="); pw.println(empty);
if (serviceb) {