diff options
author | Dianne Hackborn <hackbod@google.com> | 2015-03-03 17:04:12 -0800 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2015-03-06 16:42:03 -0800 |
commit | b9a5e4ad30c9add140fd13491419ae66e947809d (patch) | |
tree | 34000ecab4b9ef4175687e1cba78456524481a0f /services | |
parent | eb803aef3b0f55785624e6a51deae867c1a95e88 (diff) | |
download | frameworks_base-b9a5e4ad30c9add140fd13491419ae66e947809d.zip frameworks_base-b9a5e4ad30c9add140fd13491419ae66e947809d.tar.gz frameworks_base-b9a5e4ad30c9add140fd13491419ae66e947809d.tar.bz2 |
Add new debug feature to automatically create heap dumps.
Not yet working, unless you turn off SELinux enforcing.
We need to update SElinux to allow the system process
to give apps access to /data/system/heapdump/javaheap.bin.
Currently watching can only be enabled through the shell,
such as:
adb shell am set-watch-heap com.android.systemui 1024
The last number is the process pss size in bytes, so this is
asking us to warn if it goes about 1K which will be all the
time.
Change-Id: I2089e5db2927afca0bf01a363c6247ee5dcb26e8
Diffstat (limited to 'services')
3 files changed, 303 insertions, 8 deletions
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) { |