blob: b590e7738f08d083f31248f53610f0d2348e5371 [file] [log] [blame]
Adrian Roos91250682017-02-06 14:48:15 -08001/*
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
17package com.android.systemui.util.leak;
18
Dan Sandler4d90d1e2018-03-23 16:29:06 -040019import static com.android.internal.logging.MetricsLogger.VIEW_UNKNOWN;
Jason Monk196d6392018-12-20 13:25:34 -050020import static com.android.systemui.Dependency.BG_LOOPER_NAME;
Adrian Roos91250682017-02-06 14:48:15 -080021
Dan Sandler4d90d1e2018-03-23 16:29:06 -040022import android.app.ActivityManager;
23import android.content.Context;
24import android.content.Intent;
25import android.content.res.ColorStateList;
26import android.graphics.Canvas;
27import android.graphics.ColorFilter;
28import android.graphics.Paint;
29import android.graphics.PixelFormat;
30import android.graphics.PorterDuff;
31import android.graphics.Rect;
32import android.graphics.drawable.Drawable;
Adrian Roos91250682017-02-06 14:48:15 -080033import android.os.Build;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040034import android.os.Debug;
Adrian Roos91250682017-02-06 14:48:15 -080035import android.os.Handler;
36import android.os.Looper;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040037import android.os.Message;
38import android.os.Process;
Adrian Roosfb2bb3f2017-02-14 17:19:16 +010039import android.os.SystemProperties;
Jason Monk3b9357f2017-07-14 09:40:54 -040040import android.provider.Settings;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040041import android.service.quicksettings.Tile;
42import android.text.format.DateUtils;
43import android.util.Log;
44import android.util.LongSparseArray;
Adrian Roos91250682017-02-06 14:48:15 -080045
Dan Sandler4d90d1e2018-03-23 16:29:06 -040046import com.android.systemui.R;
Adrian Roos91250682017-02-06 14:48:15 -080047import com.android.systemui.SystemUI;
Jason Monk00413ce2018-12-21 11:22:30 -050048import com.android.systemui.SystemUIFactory;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040049import com.android.systemui.plugins.qs.QSTile;
50import com.android.systemui.qs.QSHost;
51import com.android.systemui.qs.tileimpl.QSTileImpl;
52
53import java.util.ArrayList;
Adrian Roos91250682017-02-06 14:48:15 -080054
Jason Monk196d6392018-12-20 13:25:34 -050055import javax.inject.Inject;
56import javax.inject.Named;
57import javax.inject.Singleton;
58
59/**
60 */
61@Singleton
Adrian Roos91250682017-02-06 14:48:15 -080062public class GarbageMonitor {
Dan Sandler4d90d1e2018-03-23 16:29:06 -040063 private static final boolean LEAK_REPORTING_ENABLED =
64 Build.IS_DEBUGGABLE
65 && SystemProperties.getBoolean("debug.enable_leak_reporting", false);
66 private static final String FORCE_ENABLE_LEAK_REPORTING = "sysui_force_enable_leak_reporting";
67
68 private static final boolean HEAP_TRACKING_ENABLED = Build.IS_DEBUGGABLE;
69 private static final boolean ENABLE_AM_HEAP_LIMIT = true; // use ActivityManager.setHeapLimit
Adrian Roos91250682017-02-06 14:48:15 -080070
71 private static final String TAG = "GarbageMonitor";
72
Dan Sandler4d90d1e2018-03-23 16:29:06 -040073 private static final long GARBAGE_INSPECTION_INTERVAL =
74 15 * DateUtils.MINUTE_IN_MILLIS; // 15 min
75 private static final long HEAP_TRACK_INTERVAL = 1 * DateUtils.MINUTE_IN_MILLIS; // 1 min
76
77 private static final int DO_GARBAGE_INSPECTION = 1000;
78 private static final int DO_HEAP_TRACK = 3000;
79
Adrian Roos91250682017-02-06 14:48:15 -080080 private static final int GARBAGE_ALLOWANCE = 5;
81
82 private final Handler mHandler;
83 private final TrackedGarbage mTrackedGarbage;
84 private final LeakReporter mLeakReporter;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040085 private final Context mContext;
86 private final ActivityManager mAm;
87 private MemoryTile mQSTile;
88 private DumpTruck mDumpTruck;
Adrian Roos91250682017-02-06 14:48:15 -080089
Dan Sandler4d90d1e2018-03-23 16:29:06 -040090 private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>();
91 private final ArrayList<Long> mPids = new ArrayList<>();
92 private int[] mPidsArray = new int[1];
93
94 private long mHeapLimit;
95
Jason Monk196d6392018-12-20 13:25:34 -050096 /**
97 */
98 @Inject
Dan Sandler4d90d1e2018-03-23 16:29:06 -040099 public GarbageMonitor(
100 Context context,
Jason Monk196d6392018-12-20 13:25:34 -0500101 @Named(BG_LOOPER_NAME) Looper bgLooper,
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400102 LeakDetector leakDetector,
Adrian Roos91250682017-02-06 14:48:15 -0800103 LeakReporter leakReporter) {
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400104 mContext = context.getApplicationContext();
105 mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
106
107 mHandler = new BackgroundHeapCheckHandler(bgLooper);
108
Adrian Roos91250682017-02-06 14:48:15 -0800109 mTrackedGarbage = leakDetector.getTrackedGarbage();
110 mLeakReporter = leakReporter;
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400111
112 mDumpTruck = new DumpTruck(mContext);
113
114 if (ENABLE_AM_HEAP_LIMIT) {
115 mHeapLimit = mContext.getResources().getInteger(R.integer.watch_heap_limit);
116 }
Adrian Roos91250682017-02-06 14:48:15 -0800117 }
118
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400119 public void startLeakMonitor() {
Adrian Roos91250682017-02-06 14:48:15 -0800120 if (mTrackedGarbage == null) {
121 return;
122 }
123
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400124 mHandler.sendEmptyMessage(DO_GARBAGE_INSPECTION);
Adrian Roos91250682017-02-06 14:48:15 -0800125 }
126
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400127 public void startHeapTracking() {
128 startTrackingProcess(
129 android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis());
130 mHandler.sendEmptyMessage(DO_HEAP_TRACK);
Adrian Roos91250682017-02-06 14:48:15 -0800131 }
132
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400133 private boolean gcAndCheckGarbage() {
Adrian Roos91250682017-02-06 14:48:15 -0800134 if (mTrackedGarbage.countOldGarbage() > GARBAGE_ALLOWANCE) {
135 Runtime.getRuntime().gc();
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400136 return true;
Adrian Roos91250682017-02-06 14:48:15 -0800137 }
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400138 return false;
Adrian Roos91250682017-02-06 14:48:15 -0800139 }
140
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400141 void reinspectGarbageAfterGc() {
Adrian Roos91250682017-02-06 14:48:15 -0800142 int count = mTrackedGarbage.countOldGarbage();
143 if (count > GARBAGE_ALLOWANCE) {
144 mLeakReporter.dumpLeak(count);
145 }
146 }
147
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400148 public ProcessMemInfo getMemInfo(int pid) {
149 return mData.get(pid);
150 }
151
152 public int[] getTrackedProcesses() {
153 return mPidsArray;
154 }
155
156 public void startTrackingProcess(long pid, String name, long start) {
157 synchronized (mPids) {
158 if (mPids.contains(pid)) return;
159
160 mPids.add(pid);
161 updatePidsArrayL();
162
163 mData.put(pid, new ProcessMemInfo(pid, name, start));
164 }
165 }
166
167 private void updatePidsArrayL() {
168 final int N = mPids.size();
169 mPidsArray = new int[N];
170 StringBuffer sb = new StringBuffer("Now tracking processes: ");
171 for (int i = 0; i < N; i++) {
172 final int p = mPids.get(i).intValue();
173 mPidsArray[i] = p;
174 sb.append(p);
175 sb.append(" ");
176 }
177 Log.v(TAG, sb.toString());
178 }
179
180 private void update() {
181 synchronized (mPids) {
182 Debug.MemoryInfo[] dinfos = mAm.getProcessMemoryInfo(mPidsArray);
183 for (int i = 0; i < dinfos.length; i++) {
184 Debug.MemoryInfo dinfo = dinfos[i];
185 if (i > mPids.size()) {
186 Log.e(TAG, "update: unknown process info received: " + dinfo);
187 break;
188 }
189 final long pid = mPids.get(i).intValue();
190 final ProcessMemInfo info = mData.get(pid);
191 info.head = (info.head + 1) % info.pss.length;
192 info.pss[info.head] = info.currentPss = dinfo.getTotalPss();
193 info.uss[info.head] = info.currentUss = dinfo.getTotalPrivateDirty();
194 if (info.currentPss > info.max) info.max = info.currentPss;
195 if (info.currentUss > info.max) info.max = info.currentUss;
196 if (info.currentPss == 0) {
197 Log.v(TAG, "update: pid " + pid + " has pss=0, it probably died");
198 mData.remove(pid);
199 }
200 }
201 for (int i = mPids.size() - 1; i >= 0; i--) {
202 final long pid = mPids.get(i).intValue();
203 if (mData.get(pid) == null) {
204 mPids.remove(i);
205 updatePidsArrayL();
206 }
207 }
208 }
209 if (mQSTile != null) mQSTile.update();
210 }
211
212 private void setTile(MemoryTile tile) {
213 mQSTile = tile;
214 if (tile != null) tile.update();
215 }
216
217 private static String formatBytes(long b) {
218 String[] SUFFIXES = {"B", "K", "M", "G", "T"};
219 int i;
220 for (i = 0; i < SUFFIXES.length; i++) {
221 if (b < 1024) break;
222 b /= 1024;
223 }
224 return b + SUFFIXES[i];
225 }
226
227 private void dumpHprofAndShare() {
228 final Intent share = mDumpTruck.captureHeaps(getTrackedProcesses()).createShareIntent();
229 mContext.startActivity(share);
230 }
231
232 private static class MemoryIconDrawable extends Drawable {
233 long pss, limit;
234 final Drawable baseIcon;
235 final Paint paint = new Paint();
236 final float dp;
237
238 MemoryIconDrawable(Context context) {
239 baseIcon = context.getDrawable(R.drawable.ic_memory).mutate();
240 dp = context.getResources().getDisplayMetrics().density;
241 paint.setColor(QSTileImpl.getColorForState(context, Tile.STATE_ACTIVE));
242 }
243
244 public void setPss(long pss) {
245 if (pss != this.pss) {
246 this.pss = pss;
247 invalidateSelf();
248 }
249 }
250
251 public void setLimit(long limit) {
252 if (limit != this.limit) {
253 this.limit = limit;
254 invalidateSelf();
255 }
256 }
257
258 @Override
259 public void draw(Canvas canvas) {
260 baseIcon.draw(canvas);
261
262 if (limit > 0 && pss > 0) {
263 float frac = Math.min(1f, (float) pss / limit);
264
265 final Rect bounds = getBounds();
266 canvas.translate(bounds.left + 8 * dp, bounds.top + 5 * dp);
267 //android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z"
268 canvas.drawRect(0, 14 * dp * (1 - frac), 8 * dp + 1, 14 * dp + 1, paint);
269 }
270 }
271
272 @Override
273 public void setBounds(int left, int top, int right, int bottom) {
274 super.setBounds(left, top, right, bottom);
275 baseIcon.setBounds(left, top, right, bottom);
276 }
277
278 @Override
279 public int getIntrinsicHeight() {
280 return baseIcon.getIntrinsicHeight();
281 }
282
283 @Override
284 public int getIntrinsicWidth() {
285 return baseIcon.getIntrinsicWidth();
286 }
287
288 @Override
289 public void setAlpha(int i) {
290 baseIcon.setAlpha(i);
291 }
292
293 @Override
294 public void setColorFilter(ColorFilter colorFilter) {
295 baseIcon.setColorFilter(colorFilter);
296 paint.setColorFilter(colorFilter);
297 }
298
299 @Override
300 public void setTint(int tint) {
301 super.setTint(tint);
302 baseIcon.setTint(tint);
303 }
304
305 @Override
306 public void setTintList(ColorStateList tint) {
307 super.setTintList(tint);
308 baseIcon.setTintList(tint);
309 }
310
311 @Override
312 public void setTintMode(PorterDuff.Mode tintMode) {
313 super.setTintMode(tintMode);
314 baseIcon.setTintMode(tintMode);
315 }
316
317 @Override
318 public int getOpacity() {
319 return PixelFormat.TRANSLUCENT;
320 }
321 }
322
323 private static class MemoryGraphIcon extends QSTile.Icon {
324 long pss, limit;
325
326 public void setPss(long pss) {
327 this.pss = pss;
328 }
329
330 public void setHeapLimit(long limit) {
331 this.limit = limit;
332 }
333
334 @Override
335 public Drawable getDrawable(Context context) {
336 final MemoryIconDrawable drawable = new MemoryIconDrawable(context);
337 drawable.setPss(pss);
338 drawable.setLimit(limit);
339 return drawable;
340 }
341 }
342
343 public static class MemoryTile extends QSTileImpl<QSTile.State> {
344 public static final String TILE_SPEC = "dbg:mem";
345
346 private final GarbageMonitor gm;
347 private ProcessMemInfo pmi;
348
Jason Monk5d577202018-12-26 15:43:06 -0500349 @Inject
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400350 public MemoryTile(QSHost host) {
351 super(host);
Jason Monk00413ce2018-12-21 11:22:30 -0500352 gm = SystemUIFactory.getInstance().getRootComponent().createGarbageMonitor();
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400353 }
354
355 @Override
356 public State newTileState() {
357 return new QSTile.State();
358 }
359
360 @Override
361 public Intent getLongClickIntent() {
362 return new Intent();
363 }
364
365 @Override
366 protected void handleClick() {
367 getHost().collapsePanels();
368 mHandler.post(gm::dumpHprofAndShare);
369 }
370
371 @Override
372 public int getMetricsCategory() {
373 return VIEW_UNKNOWN;
374 }
375
376 @Override
377 public void handleSetListening(boolean listening) {
378 if (gm != null) gm.setTile(listening ? this : null);
379
380 final ActivityManager am = mContext.getSystemService(ActivityManager.class);
381 if (listening && gm.mHeapLimit > 0) {
382 am.setWatchHeapLimit(1024 * gm.mHeapLimit); // why is this in bytes?
383 } else {
384 am.clearWatchHeapLimit();
385 }
386 }
387
388 @Override
389 public CharSequence getTileLabel() {
390 return getState().label;
391 }
392
393 @Override
394 protected void handleUpdateState(State state, Object arg) {
395 pmi = gm.getMemInfo(Process.myPid());
396 final MemoryGraphIcon icon = new MemoryGraphIcon();
397 icon.setHeapLimit(gm.mHeapLimit);
398 if (pmi != null) {
399 icon.setPss(pmi.currentPss);
400 state.label = mContext.getString(R.string.heap_dump_tile_name);
401 state.secondaryLabel =
402 String.format(
403 "pss: %s / %s",
404 formatBytes(pmi.currentPss * 1024),
405 formatBytes(gm.mHeapLimit * 1024));
406 } else {
407 icon.setPss(0);
408 state.label = "Dump SysUI";
409 state.secondaryLabel = null;
410 }
411 state.icon = icon;
412 }
413
414 public void update() {
415 refreshState();
416 }
417
418 public long getPss() {
419 return pmi != null ? pmi.currentPss : 0;
420 }
421
422 public long getHeapLimit() {
423 return gm != null ? gm.mHeapLimit : 0;
424 }
425 }
426
427 public static class ProcessMemInfo {
428 public long pid;
429 public String name;
430 public long startTime;
431 public long currentPss, currentUss;
432 public long[] pss = new long[256];
433 public long[] uss = new long[256];
434 public long max = 1;
435 public int head = 0;
436
437 public ProcessMemInfo(long pid, String name, long start) {
438 this.pid = pid;
439 this.name = name;
440 this.startTime = start;
441 }
442
443 public long getUptime() {
444 return System.currentTimeMillis() - startTime;
445 }
446 }
447
Adrian Roos91250682017-02-06 14:48:15 -0800448 public static class Service extends SystemUI {
Adrian Roos91250682017-02-06 14:48:15 -0800449 private GarbageMonitor mGarbageMonitor;
450
451 @Override
452 public void start() {
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400453 boolean forceEnable =
454 Settings.Secure.getInt(
455 mContext.getContentResolver(), FORCE_ENABLE_LEAK_REPORTING, 0)
456 != 0;
Jason Monk00413ce2018-12-21 11:22:30 -0500457 mGarbageMonitor = SystemUIFactory.getInstance().getRootComponent()
458 .createGarbageMonitor();
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400459 if (LEAK_REPORTING_ENABLED || forceEnable) {
460 mGarbageMonitor.startLeakMonitor();
461 }
462 if (HEAP_TRACKING_ENABLED || forceEnable) {
463 mGarbageMonitor.startHeapTracking();
464 }
465 }
466 }
467
468 private class BackgroundHeapCheckHandler extends Handler {
469 BackgroundHeapCheckHandler(Looper onLooper) {
470 super(onLooper);
471 if (Looper.getMainLooper().equals(onLooper)) {
472 throw new RuntimeException(
473 "BackgroundHeapCheckHandler may not run on the ui thread");
474 }
475 }
476
477 @Override
478 public void handleMessage(Message m) {
479 switch (m.what) {
480 case DO_GARBAGE_INSPECTION:
481 if (gcAndCheckGarbage()) {
482 postDelayed(GarbageMonitor.this::reinspectGarbageAfterGc, 100);
483 }
484
485 removeMessages(DO_GARBAGE_INSPECTION);
486 sendEmptyMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
487 break;
488
489 case DO_HEAP_TRACK:
490 update();
491 removeMessages(DO_HEAP_TRACK);
492 sendEmptyMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
493 break;
494 }
Adrian Roos91250682017-02-06 14:48:15 -0800495 }
496 }
497}