diff options
-rw-r--r-- | cmds/am/src/com/android/commands/am/Am.java | 22 | ||||
-rw-r--r-- | core/java/android/app/ActivityManagerNative.java | 42 | ||||
-rw-r--r-- | core/java/android/app/ActivityThread.java | 4 | ||||
-rw-r--r-- | core/java/android/app/IActivityManager.java | 9 | ||||
-rw-r--r-- | core/java/android/app/Notification.java | 1 | ||||
-rw-r--r-- | core/java/android/util/DebugUtils.java | 80 | ||||
-rw-r--r-- | core/java/com/android/internal/app/DumpHeapActivity.java | 106 | ||||
-rw-r--r-- | core/java/com/android/internal/app/ProcessStats.java | 78 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 13 | ||||
-rw-r--r-- | core/res/res/values/strings.xml | 17 | ||||
-rwxr-xr-x | core/res/res/values/symbols.xml | 4 | ||||
-rw-r--r-- | services/core/java/com/android/server/am/ActivityManagerService.java | 216 | ||||
-rw-r--r-- | services/core/java/com/android/server/am/DumpHeapProvider.java | 89 | ||||
-rw-r--r-- | services/core/java/com/android/server/am/ProcessRecord.java | 6 |
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) { |