diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java b/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java index efd6e03b0d206..fa7af0be77f10 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java @@ -16,6 +16,8 @@ package com.android.systemui.util.leak; +import android.content.ClipData; +import android.content.ClipDescription; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -47,10 +49,11 @@ public class DumpTruck { private static final String FILEPROVIDER_PATH = "leak"; private static final String TAG = "DumpTruck"; - private static final int BUFSIZ = 512 * 1024; // 512K + private static final int BUFSIZ = 1024 * 1024; // 1MB private final Context context; private Uri hprofUri; + private long pss; final StringBuilder body = new StringBuilder(); public DumpTruck(Context context) { @@ -89,6 +92,7 @@ public class DumpTruck { .append(info.currentPss) .append(" uss=") .append(info.currentUss); + pss = info.currentPss; } } if (pid == myPid) { @@ -114,6 +118,7 @@ public class DumpTruck { if (DumpTruck.zipUp(zipfile, paths)) { final File pathFile = new File(zipfile); hprofUri = FileProvider.getUriForFile(context, FILEPROVIDER_AUTHORITY, pathFile); + Log.v(TAG, "Heap dump accessible at URI: " + hprofUri); } } catch (IOException e) { Log.e(TAG, "unable to zip up heapdumps", e); @@ -138,16 +143,27 @@ public class DumpTruck { * @return share intent */ public Intent createShareIntent() { - Intent shareIntent = new Intent(Intent.ACTION_SEND); + Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - shareIntent.putExtra(Intent.EXTRA_SUBJECT, "SystemUI memory dump"); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, + String.format("SystemUI memory dump (pss=%dM)", pss / 1024)); shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString()); if (hprofUri != null) { + final ArrayList uriList = new ArrayList<>(); + uriList.add(hprofUri); shareIntent.setType("application/zip"); - shareIntent.putExtra(Intent.EXTRA_STREAM, hprofUri); + shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList); + + // Include URI in ClipData also, so that grantPermission picks it up. + // We don't use setData here because some apps interpret this as "to:". + ClipData clipdata = new ClipData(new ClipDescription("content", + new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}), + new ClipData.Item(hprofUri)); + shareIntent.setClipData(clipdata); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } return shareIntent; } diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java index aa3fd5f698026..583f6b340d47e 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java @@ -16,9 +16,13 @@ package com.android.systemui.util.leak; +import static android.service.quicksettings.Tile.STATE_ACTIVE; +import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE; + import static com.android.internal.logging.MetricsLogger.VIEW_UNKNOWN; import static com.android.systemui.Dependency.BG_LOOPER_NAME; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.Intent; @@ -38,11 +42,11 @@ import android.os.Message; import android.os.Process; import android.os.SystemProperties; import android.provider.Settings; -import android.service.quicksettings.Tile; import android.text.format.DateUtils; import android.util.Log; import android.util.LongSparseArray; +import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.SystemUIFactory; @@ -50,6 +54,8 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.tileimpl.QSTileImpl; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import javax.inject.Inject; @@ -59,7 +65,7 @@ import javax.inject.Singleton; /** */ @Singleton -public class GarbageMonitor { +public class GarbageMonitor implements Dumpable { private static final boolean LEAK_REPORTING_ENABLED = Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.enable_leak_reporting", false); @@ -77,12 +83,15 @@ public class GarbageMonitor { private static final long GARBAGE_INSPECTION_INTERVAL = 15 * DateUtils.MINUTE_IN_MILLIS; // 15 min private static final long HEAP_TRACK_INTERVAL = 1 * DateUtils.MINUTE_IN_MILLIS; // 1 min + private static final int HEAP_TRACK_HISTORY_LEN = 720; // 12 hours private static final int DO_GARBAGE_INSPECTION = 1000; private static final int DO_HEAP_TRACK = 3000; private static final int GARBAGE_ALLOWANCE = 5; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private final Handler mHandler; private final TrackedGarbage mTrackedGarbage; private final LeakReporter mLeakReporter; @@ -180,7 +189,7 @@ public class GarbageMonitor { sb.append(p); sb.append(" "); } - Log.v(TAG, sb.toString()); + if (DEBUG) Log.v(TAG, sb.toString()); } private void update() { @@ -189,18 +198,18 @@ public class GarbageMonitor { for (int i = 0; i < dinfos.length; i++) { Debug.MemoryInfo dinfo = dinfos[i]; if (i > mPids.size()) { - Log.e(TAG, "update: unknown process info received: " + dinfo); + if (DEBUG) Log.e(TAG, "update: unknown process info received: " + dinfo); break; } final long pid = mPids.get(i).intValue(); final ProcessMemInfo info = mData.get(pid); - info.head = (info.head + 1) % info.pss.length; info.pss[info.head] = info.currentPss = dinfo.getTotalPss(); info.uss[info.head] = info.currentUss = dinfo.getTotalPrivateDirty(); + info.head = (info.head + 1) % info.pss.length; if (info.currentPss > info.max) info.max = info.currentPss; if (info.currentUss > info.max) info.max = info.currentUss; if (info.currentPss == 0) { - Log.v(TAG, "update: pid " + pid + " has pss=0, it probably died"); + if (DEBUG) Log.v(TAG, "update: pid " + pid + " has pss=0, it probably died"); mData.remove(pid); } } @@ -230,11 +239,36 @@ public class GarbageMonitor { return b + SUFFIXES[i]; } - private void dumpHprofAndShare() { - final Intent share = mDumpTruck.captureHeaps(getTrackedProcesses()).createShareIntent(); - mContext.startActivity(share); + private Intent dumpHprofAndGetShareIntent() { + return mDumpTruck.captureHeaps(getTrackedProcesses()).createShareIntent(); } + @Override + public void dump(@Nullable FileDescriptor fd, PrintWriter pw, @Nullable String[] args) { + pw.println("GarbageMonitor params:"); + pw.println(String.format(" mHeapLimit=%d KB", mHeapLimit)); + pw.println(String.format(" GARBAGE_INSPECTION_INTERVAL=%d (%.1f mins)", + GARBAGE_INSPECTION_INTERVAL, + (float) GARBAGE_INSPECTION_INTERVAL / DateUtils.MINUTE_IN_MILLIS)); + final float htiMins = HEAP_TRACK_INTERVAL / DateUtils.MINUTE_IN_MILLIS; + pw.println(String.format(" HEAP_TRACK_INTERVAL=%d (%.1f mins)", + HEAP_TRACK_INTERVAL, + htiMins)); + pw.println(String.format(" HEAP_TRACK_HISTORY_LEN=%d (%.1f hr total)", + HEAP_TRACK_HISTORY_LEN, + (float) HEAP_TRACK_HISTORY_LEN * htiMins / 60f)); + + pw.println("GarbageMonitor tracked processes:"); + + for (long pid : mPids) { + final ProcessMemInfo pmi = mData.get(pid); + if (pmi != null) { + pmi.dump(fd, pw, args); + } + } + } + + private static class MemoryIconDrawable extends Drawable { long pss, limit; final Drawable baseIcon; @@ -244,7 +278,7 @@ public class GarbageMonitor { MemoryIconDrawable(Context context) { baseIcon = context.getDrawable(R.drawable.ic_memory).mutate(); dp = context.getResources().getDisplayMetrics().density; - paint.setColor(QSTileImpl.getColorForState(context, Tile.STATE_ACTIVE)); + paint.setColor(QSTileImpl.getColorForState(context, STATE_ACTIVE)); } public void setPss(long pss) { @@ -354,6 +388,7 @@ public class GarbageMonitor { private final GarbageMonitor gm; private ProcessMemInfo pmi; + private boolean dumpInProgress; @Inject public MemoryTile(QSHost host) { @@ -373,8 +408,26 @@ public class GarbageMonitor { @Override protected void handleClick() { - getHost().collapsePanels(); - mHandler.post(gm::dumpHprofAndShare); + if (dumpInProgress) return; + + dumpInProgress = true; + refreshState(); + new Thread("HeapDumpThread") { + @Override + public void run() { + try { + // wait for animations & state changes + Thread.sleep(500); + } catch (InterruptedException ignored) { } + final Intent shareIntent = gm.dumpHprofAndGetShareIntent(); + mHandler.post(() -> { + dumpInProgress = false; + refreshState(); + getHost().collapsePanels(); + mContext.startActivity(shareIntent); + }); + } + }.start(); } @Override @@ -404,9 +457,12 @@ public class GarbageMonitor { pmi = gm.getMemInfo(Process.myPid()); final MemoryGraphIcon icon = new MemoryGraphIcon(); icon.setHeapLimit(gm.mHeapLimit); + state.state = dumpInProgress ? STATE_UNAVAILABLE : STATE_ACTIVE; + state.label = dumpInProgress + ? "Dumping..." + : mContext.getString(R.string.heap_dump_tile_name); if (pmi != null) { icon.setPss(pmi.currentPss); - state.label = mContext.getString(R.string.heap_dump_tile_name); state.secondaryLabel = String.format( "pss: %s / %s", @@ -414,7 +470,6 @@ public class GarbageMonitor { formatBytes(gm.mHeapLimit * 1024)); } else { icon.setPss(0); - state.label = "Dump SysUI"; state.secondaryLabel = null; } state.icon = icon; @@ -433,13 +488,14 @@ public class GarbageMonitor { } } - public static class ProcessMemInfo { + /** */ + public static class ProcessMemInfo implements Dumpable { public long pid; public String name; public long startTime; public long currentPss, currentUss; - public long[] pss = new long[256]; - public long[] uss = new long[256]; + public long[] pss = new long[HEAP_TRACK_HISTORY_LEN]; + public long[] uss = new long[HEAP_TRACK_HISTORY_LEN]; public long max = 1; public int head = 0; @@ -452,9 +508,33 @@ public class GarbageMonitor { public long getUptime() { return System.currentTimeMillis() - startTime; } + + @Override + public void dump(@Nullable FileDescriptor fd, PrintWriter pw, @Nullable String[] args) { + pw.print("{ \"pid\": "); + pw.print(pid); + pw.print(", \"name\": \""); + pw.print(name.replace('"', '-')); + pw.print("\", \"start\": "); + pw.print(startTime); + pw.print(", \"pss\": ["); + // write pss values starting from the oldest, which is pss[head], wrapping around to + // pss[(head-1) % pss.length] + for (int i = 0; i < pss.length; i++) { + if (i > 0) pw.print(","); + pw.print(pss[(head + i) % pss.length]); + } + pw.print("], \"uss\": ["); + for (int i = 0; i < uss.length; i++) { + if (i > 0) pw.print(","); + pw.print(uss[(head + i) % uss.length]); + } + pw.println("] }"); + } } - public static class Service extends SystemUI { + /** */ + public static class Service extends SystemUI implements Dumpable { private GarbageMonitor mGarbageMonitor; @Override @@ -472,6 +552,11 @@ public class GarbageMonitor { mGarbageMonitor.startHeapTracking(); } } + + @Override + public void dump(@Nullable FileDescriptor fd, PrintWriter pw, @Nullable String[] args) { + if (mGarbageMonitor != null) mGarbageMonitor.dump(fd, pw, args); + } } private class BackgroundHeapCheckHandler extends Handler {