blob: 3c88d29f17116085a024bbaf6e0bbef9c79df6c0 [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;
Adrian Roos91250682017-02-06 14:48:15 -080020
Dan Sandler4d90d1e2018-03-23 16:29:06 -040021import android.app.ActivityManager;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.ColorStateList;
25import android.graphics.Canvas;
26import android.graphics.ColorFilter;
27import android.graphics.Paint;
28import android.graphics.PixelFormat;
29import android.graphics.PorterDuff;
30import android.graphics.Rect;
31import android.graphics.drawable.Drawable;
Adrian Roos91250682017-02-06 14:48:15 -080032import android.os.Build;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040033import android.os.Debug;
Adrian Roos91250682017-02-06 14:48:15 -080034import android.os.Handler;
35import android.os.Looper;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040036import android.os.Message;
37import android.os.Process;
Adrian Roosfb2bb3f2017-02-14 17:19:16 +010038import android.os.SystemProperties;
Jason Monk3b9357f2017-07-14 09:40:54 -040039import android.provider.Settings;
Aurimas Liutikasfd52c142018-04-17 09:50:46 -070040import androidx.annotation.VisibleForTesting;
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
46import com.android.systemui.Dependency;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040047import com.android.systemui.R;
Adrian Roos91250682017-02-06 14:48:15 -080048import com.android.systemui.SystemUI;
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
55public class GarbageMonitor {
Dan Sandler4d90d1e2018-03-23 16:29:06 -040056 private static final boolean LEAK_REPORTING_ENABLED =
57 Build.IS_DEBUGGABLE
58 && SystemProperties.getBoolean("debug.enable_leak_reporting", false);
59 private static final String FORCE_ENABLE_LEAK_REPORTING = "sysui_force_enable_leak_reporting";
60
61 private static final boolean HEAP_TRACKING_ENABLED = Build.IS_DEBUGGABLE;
62 private static final boolean ENABLE_AM_HEAP_LIMIT = true; // use ActivityManager.setHeapLimit
Adrian Roos91250682017-02-06 14:48:15 -080063
64 private static final String TAG = "GarbageMonitor";
65
Dan Sandler4d90d1e2018-03-23 16:29:06 -040066 private static final long GARBAGE_INSPECTION_INTERVAL =
67 15 * DateUtils.MINUTE_IN_MILLIS; // 15 min
68 private static final long HEAP_TRACK_INTERVAL = 1 * DateUtils.MINUTE_IN_MILLIS; // 1 min
69
70 private static final int DO_GARBAGE_INSPECTION = 1000;
71 private static final int DO_HEAP_TRACK = 3000;
72
Adrian Roos91250682017-02-06 14:48:15 -080073 private static final int GARBAGE_ALLOWANCE = 5;
74
75 private final Handler mHandler;
76 private final TrackedGarbage mTrackedGarbage;
77 private final LeakReporter mLeakReporter;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040078 private final Context mContext;
79 private final ActivityManager mAm;
80 private MemoryTile mQSTile;
81 private DumpTruck mDumpTruck;
Adrian Roos91250682017-02-06 14:48:15 -080082
Dan Sandler4d90d1e2018-03-23 16:29:06 -040083 private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>();
84 private final ArrayList<Long> mPids = new ArrayList<>();
85 private int[] mPidsArray = new int[1];
86
87 private long mHeapLimit;
88
89 public GarbageMonitor(
90 Context context,
91 Looper bgLooper,
92 LeakDetector leakDetector,
Adrian Roos91250682017-02-06 14:48:15 -080093 LeakReporter leakReporter) {
Dan Sandler4d90d1e2018-03-23 16:29:06 -040094 mContext = context.getApplicationContext();
95 mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
96
97 mHandler = new BackgroundHeapCheckHandler(bgLooper);
98
Adrian Roos91250682017-02-06 14:48:15 -080099 mTrackedGarbage = leakDetector.getTrackedGarbage();
100 mLeakReporter = leakReporter;
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400101
102 mDumpTruck = new DumpTruck(mContext);
103
104 if (ENABLE_AM_HEAP_LIMIT) {
105 mHeapLimit = mContext.getResources().getInteger(R.integer.watch_heap_limit);
106 }
Adrian Roos91250682017-02-06 14:48:15 -0800107 }
108
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400109 public void startLeakMonitor() {
Adrian Roos91250682017-02-06 14:48:15 -0800110 if (mTrackedGarbage == null) {
111 return;
112 }
113
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400114 mHandler.sendEmptyMessage(DO_GARBAGE_INSPECTION);
Adrian Roos91250682017-02-06 14:48:15 -0800115 }
116
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400117 public void startHeapTracking() {
118 startTrackingProcess(
119 android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis());
120 mHandler.sendEmptyMessage(DO_HEAP_TRACK);
Adrian Roos91250682017-02-06 14:48:15 -0800121 }
122
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400123 private boolean gcAndCheckGarbage() {
Adrian Roos91250682017-02-06 14:48:15 -0800124 if (mTrackedGarbage.countOldGarbage() > GARBAGE_ALLOWANCE) {
125 Runtime.getRuntime().gc();
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400126 return true;
Adrian Roos91250682017-02-06 14:48:15 -0800127 }
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400128 return false;
Adrian Roos91250682017-02-06 14:48:15 -0800129 }
130
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400131 void reinspectGarbageAfterGc() {
Adrian Roos91250682017-02-06 14:48:15 -0800132 int count = mTrackedGarbage.countOldGarbage();
133 if (count > GARBAGE_ALLOWANCE) {
134 mLeakReporter.dumpLeak(count);
135 }
136 }
137
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400138 public ProcessMemInfo getMemInfo(int pid) {
139 return mData.get(pid);
140 }
141
142 public int[] getTrackedProcesses() {
143 return mPidsArray;
144 }
145
146 public void startTrackingProcess(long pid, String name, long start) {
147 synchronized (mPids) {
148 if (mPids.contains(pid)) return;
149
150 mPids.add(pid);
151 updatePidsArrayL();
152
153 mData.put(pid, new ProcessMemInfo(pid, name, start));
154 }
155 }
156
157 private void updatePidsArrayL() {
158 final int N = mPids.size();
159 mPidsArray = new int[N];
160 StringBuffer sb = new StringBuffer("Now tracking processes: ");
161 for (int i = 0; i < N; i++) {
162 final int p = mPids.get(i).intValue();
163 mPidsArray[i] = p;
164 sb.append(p);
165 sb.append(" ");
166 }
167 Log.v(TAG, sb.toString());
168 }
169
170 private void update() {
171 synchronized (mPids) {
172 Debug.MemoryInfo[] dinfos = mAm.getProcessMemoryInfo(mPidsArray);
173 for (int i = 0; i < dinfos.length; i++) {
174 Debug.MemoryInfo dinfo = dinfos[i];
175 if (i > mPids.size()) {
176 Log.e(TAG, "update: unknown process info received: " + dinfo);
177 break;
178 }
179 final long pid = mPids.get(i).intValue();
180 final ProcessMemInfo info = mData.get(pid);
181 info.head = (info.head + 1) % info.pss.length;
182 info.pss[info.head] = info.currentPss = dinfo.getTotalPss();
183 info.uss[info.head] = info.currentUss = dinfo.getTotalPrivateDirty();
184 if (info.currentPss > info.max) info.max = info.currentPss;
185 if (info.currentUss > info.max) info.max = info.currentUss;
186 if (info.currentPss == 0) {
187 Log.v(TAG, "update: pid " + pid + " has pss=0, it probably died");
188 mData.remove(pid);
189 }
190 }
191 for (int i = mPids.size() - 1; i >= 0; i--) {
192 final long pid = mPids.get(i).intValue();
193 if (mData.get(pid) == null) {
194 mPids.remove(i);
195 updatePidsArrayL();
196 }
197 }
198 }
199 if (mQSTile != null) mQSTile.update();
200 }
201
202 private void setTile(MemoryTile tile) {
203 mQSTile = tile;
204 if (tile != null) tile.update();
205 }
206
207 private static String formatBytes(long b) {
208 String[] SUFFIXES = {"B", "K", "M", "G", "T"};
209 int i;
210 for (i = 0; i < SUFFIXES.length; i++) {
211 if (b < 1024) break;
212 b /= 1024;
213 }
214 return b + SUFFIXES[i];
215 }
216
217 private void dumpHprofAndShare() {
218 final Intent share = mDumpTruck.captureHeaps(getTrackedProcesses()).createShareIntent();
219 mContext.startActivity(share);
220 }
221
222 private static class MemoryIconDrawable extends Drawable {
223 long pss, limit;
224 final Drawable baseIcon;
225 final Paint paint = new Paint();
226 final float dp;
227
228 MemoryIconDrawable(Context context) {
229 baseIcon = context.getDrawable(R.drawable.ic_memory).mutate();
230 dp = context.getResources().getDisplayMetrics().density;
231 paint.setColor(QSTileImpl.getColorForState(context, Tile.STATE_ACTIVE));
232 }
233
234 public void setPss(long pss) {
235 if (pss != this.pss) {
236 this.pss = pss;
237 invalidateSelf();
238 }
239 }
240
241 public void setLimit(long limit) {
242 if (limit != this.limit) {
243 this.limit = limit;
244 invalidateSelf();
245 }
246 }
247
248 @Override
249 public void draw(Canvas canvas) {
250 baseIcon.draw(canvas);
251
252 if (limit > 0 && pss > 0) {
253 float frac = Math.min(1f, (float) pss / limit);
254
255 final Rect bounds = getBounds();
256 canvas.translate(bounds.left + 8 * dp, bounds.top + 5 * dp);
257 //android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z"
258 canvas.drawRect(0, 14 * dp * (1 - frac), 8 * dp + 1, 14 * dp + 1, paint);
259 }
260 }
261
262 @Override
263 public void setBounds(int left, int top, int right, int bottom) {
264 super.setBounds(left, top, right, bottom);
265 baseIcon.setBounds(left, top, right, bottom);
266 }
267
268 @Override
269 public int getIntrinsicHeight() {
270 return baseIcon.getIntrinsicHeight();
271 }
272
273 @Override
274 public int getIntrinsicWidth() {
275 return baseIcon.getIntrinsicWidth();
276 }
277
278 @Override
279 public void setAlpha(int i) {
280 baseIcon.setAlpha(i);
281 }
282
283 @Override
284 public void setColorFilter(ColorFilter colorFilter) {
285 baseIcon.setColorFilter(colorFilter);
286 paint.setColorFilter(colorFilter);
287 }
288
289 @Override
290 public void setTint(int tint) {
291 super.setTint(tint);
292 baseIcon.setTint(tint);
293 }
294
295 @Override
296 public void setTintList(ColorStateList tint) {
297 super.setTintList(tint);
298 baseIcon.setTintList(tint);
299 }
300
301 @Override
302 public void setTintMode(PorterDuff.Mode tintMode) {
303 super.setTintMode(tintMode);
304 baseIcon.setTintMode(tintMode);
305 }
306
307 @Override
308 public int getOpacity() {
309 return PixelFormat.TRANSLUCENT;
310 }
311 }
312
313 private static class MemoryGraphIcon extends QSTile.Icon {
314 long pss, limit;
315
316 public void setPss(long pss) {
317 this.pss = pss;
318 }
319
320 public void setHeapLimit(long limit) {
321 this.limit = limit;
322 }
323
324 @Override
325 public Drawable getDrawable(Context context) {
326 final MemoryIconDrawable drawable = new MemoryIconDrawable(context);
327 drawable.setPss(pss);
328 drawable.setLimit(limit);
329 return drawable;
330 }
331 }
332
333 public static class MemoryTile extends QSTileImpl<QSTile.State> {
334 public static final String TILE_SPEC = "dbg:mem";
335
336 private final GarbageMonitor gm;
337 private ProcessMemInfo pmi;
338
339 public MemoryTile(QSHost host) {
340 super(host);
341 gm = Dependency.get(GarbageMonitor.class);
342 }
343
344 @Override
345 public State newTileState() {
346 return new QSTile.State();
347 }
348
349 @Override
350 public Intent getLongClickIntent() {
351 return new Intent();
352 }
353
354 @Override
355 protected void handleClick() {
356 getHost().collapsePanels();
357 mHandler.post(gm::dumpHprofAndShare);
358 }
359
360 @Override
361 public int getMetricsCategory() {
362 return VIEW_UNKNOWN;
363 }
364
365 @Override
366 public void handleSetListening(boolean listening) {
367 if (gm != null) gm.setTile(listening ? this : null);
368
369 final ActivityManager am = mContext.getSystemService(ActivityManager.class);
370 if (listening && gm.mHeapLimit > 0) {
371 am.setWatchHeapLimit(1024 * gm.mHeapLimit); // why is this in bytes?
372 } else {
373 am.clearWatchHeapLimit();
374 }
375 }
376
377 @Override
378 public CharSequence getTileLabel() {
379 return getState().label;
380 }
381
382 @Override
383 protected void handleUpdateState(State state, Object arg) {
384 pmi = gm.getMemInfo(Process.myPid());
385 final MemoryGraphIcon icon = new MemoryGraphIcon();
386 icon.setHeapLimit(gm.mHeapLimit);
387 if (pmi != null) {
388 icon.setPss(pmi.currentPss);
389 state.label = mContext.getString(R.string.heap_dump_tile_name);
390 state.secondaryLabel =
391 String.format(
392 "pss: %s / %s",
393 formatBytes(pmi.currentPss * 1024),
394 formatBytes(gm.mHeapLimit * 1024));
395 } else {
396 icon.setPss(0);
397 state.label = "Dump SysUI";
398 state.secondaryLabel = null;
399 }
400 state.icon = icon;
401 }
402
403 public void update() {
404 refreshState();
405 }
406
407 public long getPss() {
408 return pmi != null ? pmi.currentPss : 0;
409 }
410
411 public long getHeapLimit() {
412 return gm != null ? gm.mHeapLimit : 0;
413 }
414 }
415
416 public static class ProcessMemInfo {
417 public long pid;
418 public String name;
419 public long startTime;
420 public long currentPss, currentUss;
421 public long[] pss = new long[256];
422 public long[] uss = new long[256];
423 public long max = 1;
424 public int head = 0;
425
426 public ProcessMemInfo(long pid, String name, long start) {
427 this.pid = pid;
428 this.name = name;
429 this.startTime = start;
430 }
431
432 public long getUptime() {
433 return System.currentTimeMillis() - startTime;
434 }
435 }
436
Adrian Roos91250682017-02-06 14:48:15 -0800437 public static class Service extends SystemUI {
Adrian Roos91250682017-02-06 14:48:15 -0800438 private GarbageMonitor mGarbageMonitor;
439
440 @Override
441 public void start() {
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400442 boolean forceEnable =
443 Settings.Secure.getInt(
444 mContext.getContentResolver(), FORCE_ENABLE_LEAK_REPORTING, 0)
445 != 0;
Adrian Roos91250682017-02-06 14:48:15 -0800446 mGarbageMonitor = Dependency.get(GarbageMonitor.class);
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400447 if (LEAK_REPORTING_ENABLED || forceEnable) {
448 mGarbageMonitor.startLeakMonitor();
449 }
450 if (HEAP_TRACKING_ENABLED || forceEnable) {
451 mGarbageMonitor.startHeapTracking();
452 }
453 }
454 }
455
456 private class BackgroundHeapCheckHandler extends Handler {
457 BackgroundHeapCheckHandler(Looper onLooper) {
458 super(onLooper);
459 if (Looper.getMainLooper().equals(onLooper)) {
460 throw new RuntimeException(
461 "BackgroundHeapCheckHandler may not run on the ui thread");
462 }
463 }
464
465 @Override
466 public void handleMessage(Message m) {
467 switch (m.what) {
468 case DO_GARBAGE_INSPECTION:
469 if (gcAndCheckGarbage()) {
470 postDelayed(GarbageMonitor.this::reinspectGarbageAfterGc, 100);
471 }
472
473 removeMessages(DO_GARBAGE_INSPECTION);
474 sendEmptyMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
475 break;
476
477 case DO_HEAP_TRACK:
478 update();
479 removeMessages(DO_HEAP_TRACK);
480 sendEmptyMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
481 break;
482 }
Adrian Roos91250682017-02-06 14:48:15 -0800483 }
484 }
485}