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
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user