Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 646d66b0 authored by Dan Sandler's avatar Dan Sandler
Browse files

Add PSS info to dumpsys output.

We now track the last 12 hours of PSS measurements in SysUI
and emit them in bugreports.

While we're at it, tidy up some of the heap dumping code
(dump on separate thread, update tile state, tweak sharing
extras to fix filename when sharing to Drive).

Bug: 133671238
Test: adb shell dumpsys activity service SystemUI
Change-Id: I862f6e78ed49cf669786610fec0768ebb3b24438
parent 6d371e09
Loading
Loading
Loading
Loading
+20 −4
Original line number Diff line number Diff line
@@ -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<Uri> 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;
    }
+103 −18
Original line number Diff line number Diff line
@@ -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() {
            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();
            mHandler.post(gm::dumpHprofAndShare);
                        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;
        }
    }

    public static class Service extends SystemUI {
        @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 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 {