• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.util.leak;
18 
19 import static android.service.quicksettings.Tile.STATE_ACTIVE;
20 import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
21 
22 import static com.android.internal.logging.MetricsLogger.VIEW_UNKNOWN;
23 
24 import android.annotation.Nullable;
25 import android.app.ActivityManager;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.res.ColorStateList;
29 import android.graphics.Canvas;
30 import android.graphics.Color;
31 import android.graphics.ColorFilter;
32 import android.graphics.Paint;
33 import android.graphics.PixelFormat;
34 import android.graphics.PorterDuff;
35 import android.graphics.Rect;
36 import android.graphics.drawable.Drawable;
37 import android.os.Build;
38 import android.os.Handler;
39 import android.os.Looper;
40 import android.os.Process;
41 import android.os.SystemProperties;
42 import android.provider.Settings;
43 import android.text.format.DateUtils;
44 import android.util.Log;
45 import android.util.LongSparseArray;
46 import android.view.View;
47 
48 import com.android.internal.logging.MetricsLogger;
49 import com.android.systemui.CoreStartable;
50 import com.android.systemui.Dumpable;
51 import com.android.systemui.R;
52 import com.android.systemui.dagger.SysUISingleton;
53 import com.android.systemui.dagger.qualifiers.Background;
54 import com.android.systemui.dagger.qualifiers.Main;
55 import com.android.systemui.dump.DumpManager;
56 import com.android.systemui.plugins.ActivityStarter;
57 import com.android.systemui.plugins.FalsingManager;
58 import com.android.systemui.plugins.qs.QSTile;
59 import com.android.systemui.plugins.statusbar.StatusBarStateController;
60 import com.android.systemui.qs.QSHost;
61 import com.android.systemui.qs.logging.QSLogger;
62 import com.android.systemui.qs.tileimpl.QSTileImpl;
63 import com.android.systemui.util.concurrency.DelayableExecutor;
64 import com.android.systemui.util.concurrency.MessageRouter;
65 
66 import java.io.PrintWriter;
67 import java.util.ArrayList;
68 import java.util.List;
69 
70 import javax.inject.Inject;
71 
72 /**
73  * Suite of tools to periodically inspect the System UI heap and possibly prompt the user to
74  * capture heap dumps and report them. Includes the implementation of the "Dump SysUI Heap"
75  * quick settings tile.
76  */
77 @SysUISingleton
78 public class GarbageMonitor implements Dumpable {
79     // Feature switches
80     // ================
81 
82     // Whether to use TrackedGarbage to trigger LeakReporter. Off by default unless you set the
83     // appropriate sysprop on a userdebug device.
84     public static final boolean LEAK_REPORTING_ENABLED = Build.IS_DEBUGGABLE
85             && SystemProperties.getBoolean("debug.enable_leak_reporting", false);
86     public static final String FORCE_ENABLE_LEAK_REPORTING = "sysui_force_enable_leak_reporting";
87 
88     // Heap tracking: watch the current memory levels and update the MemoryTile if available.
89     // On for all userdebug devices.
90     public static final boolean HEAP_TRACKING_ENABLED = Build.IS_DEBUGGABLE;
91 
92     // Tell QSTileHost.java to toss this into the default tileset?
93     public static final boolean ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS = true;
94 
95     // whether to use ActivityManager.setHeapLimit (and post a notification to the user asking
96     // to dump the heap). Off by default unless you set the appropriate sysprop on userdebug
97     private static final boolean ENABLE_AM_HEAP_LIMIT = Build.IS_DEBUGGABLE
98             && SystemProperties.getBoolean("debug.enable_sysui_heap_limit", false);
99 
100     // Tuning params
101     // =============
102 
103     // threshold for setHeapLimit(), in KB (overrides R.integer.watch_heap_limit)
104     private static final String SETTINGS_KEY_AM_HEAP_LIMIT = "systemui_am_heap_limit";
105 
106     private static final long GARBAGE_INSPECTION_INTERVAL =
107             15 * DateUtils.MINUTE_IN_MILLIS; // 15 min
108     private static final long HEAP_TRACK_INTERVAL = 1 * DateUtils.MINUTE_IN_MILLIS; // 1 min
109     private static final int HEAP_TRACK_HISTORY_LEN = 720; // 12 hours
110 
111     private static final int DO_GARBAGE_INSPECTION = 1000;
112     private static final int DO_HEAP_TRACK = 3000;
113 
114     static final int GARBAGE_ALLOWANCE = 5;
115 
116     private static final String TAG = "GarbageMonitor";
117     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
118 
119     private final MessageRouter mMessageRouter;
120     private final TrackedGarbage mTrackedGarbage;
121     private final LeakReporter mLeakReporter;
122     private final Context mContext;
123     private final DelayableExecutor mDelayableExecutor;
124     private MemoryTile mQSTile;
125     private final DumpTruck mDumpTruck;
126 
127     private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>();
128     private final ArrayList<Long> mPids = new ArrayList<>();
129 
130     private long mHeapLimit;
131 
132     /**
133      */
134     @Inject
GarbageMonitor( Context context, @Background DelayableExecutor delayableExecutor, @Background MessageRouter messageRouter, LeakDetector leakDetector, LeakReporter leakReporter, DumpManager dumpManager)135     public GarbageMonitor(
136             Context context,
137             @Background DelayableExecutor delayableExecutor,
138             @Background MessageRouter messageRouter,
139             LeakDetector leakDetector,
140             LeakReporter leakReporter,
141             DumpManager dumpManager) {
142         mContext = context.getApplicationContext();
143 
144         mDelayableExecutor = delayableExecutor;
145         mMessageRouter = messageRouter;
146         mMessageRouter.subscribeTo(DO_GARBAGE_INSPECTION, this::doGarbageInspection);
147         mMessageRouter.subscribeTo(DO_HEAP_TRACK, this::doHeapTrack);
148 
149         mTrackedGarbage = leakDetector.getTrackedGarbage();
150         mLeakReporter = leakReporter;
151 
152         mDumpTruck = new DumpTruck(mContext, this);
153 
154         dumpManager.registerDumpable(getClass().getSimpleName(), this);
155 
156         if (ENABLE_AM_HEAP_LIMIT) {
157             mHeapLimit = Settings.Global.getInt(context.getContentResolver(),
158                     SETTINGS_KEY_AM_HEAP_LIMIT,
159                     mContext.getResources().getInteger(R.integer.watch_heap_limit));
160         }
161     }
162 
startLeakMonitor()163     public void startLeakMonitor() {
164         if (mTrackedGarbage == null) {
165             return;
166         }
167 
168         mMessageRouter.sendMessage(DO_GARBAGE_INSPECTION);
169     }
170 
startHeapTracking()171     public void startHeapTracking() {
172         startTrackingProcess(
173                 android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis());
174         mMessageRouter.sendMessage(DO_HEAP_TRACK);
175     }
176 
gcAndCheckGarbage()177     private boolean gcAndCheckGarbage() {
178         if (mTrackedGarbage.countOldGarbage() > GARBAGE_ALLOWANCE) {
179             Runtime.getRuntime().gc();
180             return true;
181         }
182         return false;
183     }
184 
reinspectGarbageAfterGc()185     void reinspectGarbageAfterGc() {
186         int count = mTrackedGarbage.countOldGarbage();
187         if (count > GARBAGE_ALLOWANCE) {
188             mLeakReporter.dumpLeak(count);
189         }
190     }
191 
getMemInfo(int pid)192     public ProcessMemInfo getMemInfo(int pid) {
193         return mData.get(pid);
194     }
195 
getTrackedProcesses()196     public List<Long> getTrackedProcesses() {
197         return mPids;
198     }
199 
startTrackingProcess(long pid, String name, long start)200     public void startTrackingProcess(long pid, String name, long start) {
201         synchronized (mPids) {
202             if (mPids.contains(pid)) return;
203 
204             mPids.add(pid);
205             logPids();
206 
207             mData.put(pid, new ProcessMemInfo(pid, name, start));
208         }
209     }
210 
logPids()211     private void logPids() {
212         if (DEBUG) {
213             StringBuffer sb = new StringBuffer("Now tracking processes: ");
214             for (int i = 0; i < mPids.size(); i++) {
215                 final int p = mPids.get(i).intValue();
216                 sb.append(" ");
217             }
218             Log.v(TAG, sb.toString());
219         }
220     }
221 
update()222     private void update() {
223         synchronized (mPids) {
224             for (int i = 0; i < mPids.size(); i++) {
225                 final int pid = mPids.get(i).intValue();
226                 // rssValues contains [VmRSS, RssFile, RssAnon, VmSwap].
227                 long[] rssValues = Process.getRss(pid);
228                 if (rssValues == null && rssValues.length == 0) {
229                     if (DEBUG) Log.e(TAG, "update: Process.getRss() didn't provide any values.");
230                     break;
231                 }
232                 long rss = rssValues[0];
233                 final ProcessMemInfo info = mData.get(pid);
234                 info.rss[info.head] = info.currentRss = rss;
235                 info.head = (info.head + 1) % info.rss.length;
236                 if (info.currentRss > info.max) info.max = info.currentRss;
237                 if (info.currentRss == 0) {
238                     if (DEBUG) Log.v(TAG, "update: pid " + pid + " has rss=0, it probably died");
239                     mData.remove(pid);
240                 }
241             }
242             for (int i = mPids.size() - 1; i >= 0; i--) {
243                 final long pid = mPids.get(i).intValue();
244                 if (mData.get(pid) == null) {
245                     mPids.remove(i);
246                     logPids();
247                 }
248             }
249         }
250         if (mQSTile != null) mQSTile.update();
251     }
252 
setTile(MemoryTile tile)253     private void setTile(MemoryTile tile) {
254         mQSTile = tile;
255         if (tile != null) tile.update();
256     }
257 
formatBytes(long b)258     private static String formatBytes(long b) {
259         String[] SUFFIXES = {"B", "K", "M", "G", "T"};
260         int i;
261         for (i = 0; i < SUFFIXES.length; i++) {
262             if (b < 1024) break;
263             b /= 1024;
264         }
265         return b + SUFFIXES[i];
266     }
267 
dumpHprofAndGetShareIntent()268     private Intent dumpHprofAndGetShareIntent() {
269         return mDumpTruck.captureHeaps(getTrackedProcesses()).createShareIntent();
270     }
271 
272     @Override
dump(PrintWriter pw, @Nullable String[] args)273     public void dump(PrintWriter pw, @Nullable String[] args) {
274         pw.println("GarbageMonitor params:");
275         pw.println(String.format("   mHeapLimit=%d KB", mHeapLimit));
276         pw.println(String.format("   GARBAGE_INSPECTION_INTERVAL=%d (%.1f mins)",
277                 GARBAGE_INSPECTION_INTERVAL,
278                 (float) GARBAGE_INSPECTION_INTERVAL / DateUtils.MINUTE_IN_MILLIS));
279         final float htiMins = HEAP_TRACK_INTERVAL / DateUtils.MINUTE_IN_MILLIS;
280         pw.println(String.format("   HEAP_TRACK_INTERVAL=%d (%.1f mins)",
281                 HEAP_TRACK_INTERVAL,
282                 htiMins));
283         pw.println(String.format("   HEAP_TRACK_HISTORY_LEN=%d (%.1f hr total)",
284                 HEAP_TRACK_HISTORY_LEN,
285                 (float) HEAP_TRACK_HISTORY_LEN * htiMins / 60f));
286 
287         pw.println("GarbageMonitor tracked processes:");
288 
289         for (long pid : mPids) {
290             final ProcessMemInfo pmi = mData.get(pid);
291             if (pmi != null) {
292                 pmi.dump(pw, args);
293             }
294         }
295     }
296 
297 
298     private static class MemoryIconDrawable extends Drawable {
299         long rss, limit;
300         final Drawable baseIcon;
301         final Paint paint = new Paint();
302         final float dp;
303 
MemoryIconDrawable(Context context)304         MemoryIconDrawable(Context context) {
305             baseIcon = context.getDrawable(R.drawable.ic_memory).mutate();
306             dp = context.getResources().getDisplayMetrics().density;
307             paint.setColor(Color.WHITE);
308         }
309 
setRss(long rss)310         public void setRss(long rss) {
311             if (rss != this.rss) {
312                 this.rss = rss;
313                 invalidateSelf();
314             }
315         }
316 
setLimit(long limit)317         public void setLimit(long limit) {
318             if (limit != this.limit) {
319                 this.limit = limit;
320                 invalidateSelf();
321             }
322         }
323 
324         @Override
draw(Canvas canvas)325         public void draw(Canvas canvas) {
326             baseIcon.draw(canvas);
327 
328             if (limit > 0 && rss > 0) {
329                 float frac = Math.min(1f, (float) rss / limit);
330 
331                 final Rect bounds = getBounds();
332                 canvas.translate(bounds.left + 8 * dp, bounds.top + 5 * dp);
333                 //android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z"
334                 canvas.drawRect(0, 14 * dp * (1 - frac), 8 * dp + 1, 14 * dp + 1, paint);
335             }
336         }
337 
338         @Override
setBounds(int left, int top, int right, int bottom)339         public void setBounds(int left, int top, int right, int bottom) {
340             super.setBounds(left, top, right, bottom);
341             baseIcon.setBounds(left, top, right, bottom);
342         }
343 
344         @Override
getIntrinsicHeight()345         public int getIntrinsicHeight() {
346             return baseIcon.getIntrinsicHeight();
347         }
348 
349         @Override
getIntrinsicWidth()350         public int getIntrinsicWidth() {
351             return baseIcon.getIntrinsicWidth();
352         }
353 
354         @Override
setAlpha(int i)355         public void setAlpha(int i) {
356             baseIcon.setAlpha(i);
357         }
358 
359         @Override
setColorFilter(ColorFilter colorFilter)360         public void setColorFilter(ColorFilter colorFilter) {
361             baseIcon.setColorFilter(colorFilter);
362             paint.setColorFilter(colorFilter);
363         }
364 
365         @Override
setTint(int tint)366         public void setTint(int tint) {
367             super.setTint(tint);
368             baseIcon.setTint(tint);
369         }
370 
371         @Override
setTintList(ColorStateList tint)372         public void setTintList(ColorStateList tint) {
373             super.setTintList(tint);
374             baseIcon.setTintList(tint);
375         }
376 
377         @Override
setTintMode(PorterDuff.Mode tintMode)378         public void setTintMode(PorterDuff.Mode tintMode) {
379             super.setTintMode(tintMode);
380             baseIcon.setTintMode(tintMode);
381         }
382 
383         @Override
getOpacity()384         public int getOpacity() {
385             return PixelFormat.TRANSLUCENT;
386         }
387     }
388 
389     private static class MemoryGraphIcon extends QSTile.Icon {
390         long rss, limit;
391 
setRss(long rss)392         public void setRss(long rss) {
393             this.rss = rss;
394         }
395 
setHeapLimit(long limit)396         public void setHeapLimit(long limit) {
397             this.limit = limit;
398         }
399 
400         @Override
getDrawable(Context context)401         public Drawable getDrawable(Context context) {
402             final MemoryIconDrawable drawable = new MemoryIconDrawable(context);
403             drawable.setRss(rss);
404             drawable.setLimit(limit);
405             return drawable;
406         }
407     }
408 
409     public static class MemoryTile extends QSTileImpl<QSTile.State> {
410         public static final String TILE_SPEC = "dbg:mem";
411 
412         private final GarbageMonitor gm;
413         private ProcessMemInfo pmi;
414         private boolean dumpInProgress;
415 
416         @Inject
MemoryTile( QSHost host, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, GarbageMonitor monitor )417         public MemoryTile(
418                 QSHost host,
419                 @Background Looper backgroundLooper,
420                 @Main Handler mainHandler,
421                 FalsingManager falsingManager,
422                 MetricsLogger metricsLogger,
423                 StatusBarStateController statusBarStateController,
424                 ActivityStarter activityStarter,
425                 QSLogger qsLogger,
426                 GarbageMonitor monitor
427         ) {
428             super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
429                     statusBarStateController, activityStarter, qsLogger);
430             gm = monitor;
431         }
432 
433         @Override
newTileState()434         public State newTileState() {
435             return new QSTile.State();
436         }
437 
438         @Override
getLongClickIntent()439         public Intent getLongClickIntent() {
440             return new Intent();
441         }
442 
443         @Override
handleClick(@ullable View view)444         protected void handleClick(@Nullable View view) {
445             if (dumpInProgress) return;
446 
447             dumpInProgress = true;
448             refreshState();
449             new Thread("HeapDumpThread") {
450                 @Override
451                 public void run() {
452                     try {
453                         // wait for animations & state changes
454                         Thread.sleep(500);
455                     } catch (InterruptedException ignored) { }
456                     final Intent shareIntent = gm.dumpHprofAndGetShareIntent();
457                     mHandler.post(() -> {
458                         dumpInProgress = false;
459                         refreshState();
460                         getHost().collapsePanels();
461                         mActivityStarter.postStartActivityDismissingKeyguard(shareIntent, 0);
462                     });
463                 }
464             }.start();
465         }
466 
467         @Override
getMetricsCategory()468         public int getMetricsCategory() {
469             return VIEW_UNKNOWN;
470         }
471 
472         @Override
handleSetListening(boolean listening)473         public void handleSetListening(boolean listening) {
474             super.handleSetListening(listening);
475             if (gm != null) gm.setTile(listening ? this : null);
476 
477             final ActivityManager am = mContext.getSystemService(ActivityManager.class);
478             if (listening && gm.mHeapLimit > 0) {
479                 am.setWatchHeapLimit(1024 * gm.mHeapLimit); // why is this in bytes?
480             } else {
481                 am.clearWatchHeapLimit();
482             }
483         }
484 
485         @Override
getTileLabel()486         public CharSequence getTileLabel() {
487             return getState().label;
488         }
489 
490         @Override
handleUpdateState(State state, Object arg)491         protected void handleUpdateState(State state, Object arg) {
492             pmi = gm.getMemInfo(Process.myPid());
493             final MemoryGraphIcon icon = new MemoryGraphIcon();
494             icon.setHeapLimit(gm.mHeapLimit);
495             state.state = dumpInProgress ? STATE_UNAVAILABLE : STATE_ACTIVE;
496             state.label = dumpInProgress
497                     ? "Dumping..."
498                     : mContext.getString(R.string.heap_dump_tile_name);
499             if (pmi != null) {
500                 icon.setRss(pmi.currentRss);
501                 state.secondaryLabel =
502                         String.format(
503                                 "rss: %s / %s",
504                                 formatBytes(pmi.currentRss * 1024),
505                                 formatBytes(gm.mHeapLimit * 1024));
506             } else {
507                 icon.setRss(0);
508                 state.secondaryLabel = null;
509             }
510             state.icon = icon;
511         }
512 
update()513         public void update() {
514             refreshState();
515         }
516 
getRss()517         public long getRss() {
518             return pmi != null ? pmi.currentRss : 0;
519         }
520 
getHeapLimit()521         public long getHeapLimit() {
522             return gm != null ? gm.mHeapLimit : 0;
523         }
524     }
525 
526     /** */
527     public static class ProcessMemInfo implements Dumpable {
528         public long pid;
529         public String name;
530         public long startTime;
531         public long currentRss;
532         public long[] rss = new long[HEAP_TRACK_HISTORY_LEN];
533         public long max = 1;
534         public int head = 0;
535 
ProcessMemInfo(long pid, String name, long start)536         public ProcessMemInfo(long pid, String name, long start) {
537             this.pid = pid;
538             this.name = name;
539             this.startTime = start;
540         }
541 
getUptime()542         public long getUptime() {
543             return System.currentTimeMillis() - startTime;
544         }
545 
546         @Override
dump(PrintWriter pw, @Nullable String[] args)547         public void dump(PrintWriter pw, @Nullable String[] args) {
548             pw.print("{ \"pid\": ");
549             pw.print(pid);
550             pw.print(", \"name\": \"");
551             pw.print(name.replace('"', '-'));
552             pw.print("\", \"start\": ");
553             pw.print(startTime);
554             pw.print(", \"rss\": [");
555             // write rss values starting from the oldest, which is rss[head], wrapping around to
556             // rss[(head-1) % rss.length]
557             for (int i = 0; i < rss.length; i++) {
558                 if (i > 0) pw.print(",");
559                 pw.print(rss[(head + i) % rss.length]);
560             }
561             pw.println("] }");
562         }
563     }
564 
565     /** */
566     @SysUISingleton
567     public static class Service implements CoreStartable,  Dumpable {
568         private final Context mContext;
569         private final GarbageMonitor mGarbageMonitor;
570 
571         @Inject
Service(Context context, GarbageMonitor garbageMonitor)572         public Service(Context context, GarbageMonitor garbageMonitor) {
573             mContext = context;
574             mGarbageMonitor = garbageMonitor;
575         }
576 
577         @Override
start()578         public void start() {
579             boolean forceEnable =
580                     Settings.Secure.getInt(
581                                     mContext.getContentResolver(), FORCE_ENABLE_LEAK_REPORTING, 0)
582                             != 0;
583             if (LEAK_REPORTING_ENABLED || forceEnable) {
584                 mGarbageMonitor.startLeakMonitor();
585             }
586             if (HEAP_TRACKING_ENABLED || forceEnable) {
587                 mGarbageMonitor.startHeapTracking();
588             }
589         }
590 
591         @Override
dump(PrintWriter pw, @Nullable String[] args)592         public void dump(PrintWriter pw, @Nullable String[] args) {
593             if (mGarbageMonitor != null) mGarbageMonitor.dump(pw, args);
594         }
595     }
596 
doGarbageInspection(int id)597     private void doGarbageInspection(int id) {
598         if (gcAndCheckGarbage()) {
599             mDelayableExecutor.executeDelayed(this::reinspectGarbageAfterGc, 100);
600         }
601 
602         mMessageRouter.cancelMessages(DO_GARBAGE_INSPECTION);
603         mMessageRouter.sendMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
604     }
605 
doHeapTrack(int id)606     private void doHeapTrack(int id) {
607         update();
608         mMessageRouter.cancelMessages(DO_HEAP_TRACK);
609         mMessageRouter.sendMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
610     }
611 }
612