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