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 import static com.android.systemui.Dependency.BG_LOOPER_NAME; 24 25 import android.annotation.Nullable; 26 import android.app.ActivityManager; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.res.ColorStateList; 30 import android.graphics.Canvas; 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.Debug; 39 import android.os.Handler; 40 import android.os.Looper; 41 import android.os.Message; 42 import android.os.Process; 43 import android.os.SystemProperties; 44 import android.provider.Settings; 45 import android.text.format.DateUtils; 46 import android.util.Log; 47 import android.util.LongSparseArray; 48 49 import com.android.systemui.Dumpable; 50 import com.android.systemui.R; 51 import com.android.systemui.SystemUI; 52 import com.android.systemui.SystemUIFactory; 53 import com.android.systemui.plugins.qs.QSTile; 54 import com.android.systemui.qs.QSHost; 55 import com.android.systemui.qs.tileimpl.QSTileImpl; 56 57 import java.io.FileDescriptor; 58 import java.io.PrintWriter; 59 import java.util.ArrayList; 60 61 import javax.inject.Inject; 62 import javax.inject.Named; 63 import javax.inject.Singleton; 64 65 /** 66 */ 67 @Singleton 68 public class GarbageMonitor implements Dumpable { 69 private static final boolean LEAK_REPORTING_ENABLED = 70 Build.IS_DEBUGGABLE 71 && SystemProperties.getBoolean("debug.enable_leak_reporting", false); 72 private static final String FORCE_ENABLE_LEAK_REPORTING = "sysui_force_enable_leak_reporting"; 73 74 private static final boolean HEAP_TRACKING_ENABLED = Build.IS_DEBUGGABLE; 75 76 // whether to use ActivityManager.setHeapLimit 77 private static final boolean ENABLE_AM_HEAP_LIMIT = Build.IS_DEBUGGABLE; 78 // heap limit value, in KB (overrides R.integer.watch_heap_limit) 79 private static final String SETTINGS_KEY_AM_HEAP_LIMIT = "systemui_am_heap_limit"; 80 81 private static final String TAG = "GarbageMonitor"; 82 83 private static final long GARBAGE_INSPECTION_INTERVAL = 84 15 * DateUtils.MINUTE_IN_MILLIS; // 15 min 85 private static final long HEAP_TRACK_INTERVAL = 1 * DateUtils.MINUTE_IN_MILLIS; // 1 min 86 private static final int HEAP_TRACK_HISTORY_LEN = 720; // 12 hours 87 88 private static final int DO_GARBAGE_INSPECTION = 1000; 89 private static final int DO_HEAP_TRACK = 3000; 90 91 private static final int GARBAGE_ALLOWANCE = 5; 92 93 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 94 95 private final Handler mHandler; 96 private final TrackedGarbage mTrackedGarbage; 97 private final LeakReporter mLeakReporter; 98 private final Context mContext; 99 private final ActivityManager mAm; 100 private MemoryTile mQSTile; 101 private DumpTruck mDumpTruck; 102 103 private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>(); 104 private final ArrayList<Long> mPids = new ArrayList<>(); 105 private int[] mPidsArray = new int[1]; 106 107 private long mHeapLimit; 108 109 /** 110 */ 111 @Inject GarbageMonitor( Context context, @Named(BG_LOOPER_NAME) Looper bgLooper, LeakDetector leakDetector, LeakReporter leakReporter)112 public GarbageMonitor( 113 Context context, 114 @Named(BG_LOOPER_NAME) Looper bgLooper, 115 LeakDetector leakDetector, 116 LeakReporter leakReporter) { 117 mContext = context.getApplicationContext(); 118 mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 119 120 mHandler = new BackgroundHeapCheckHandler(bgLooper); 121 122 mTrackedGarbage = leakDetector.getTrackedGarbage(); 123 mLeakReporter = leakReporter; 124 125 mDumpTruck = new DumpTruck(mContext); 126 127 if (ENABLE_AM_HEAP_LIMIT) { 128 mHeapLimit = Settings.Global.getInt(context.getContentResolver(), 129 SETTINGS_KEY_AM_HEAP_LIMIT, 130 mContext.getResources().getInteger(R.integer.watch_heap_limit)); 131 } 132 } 133 startLeakMonitor()134 public void startLeakMonitor() { 135 if (mTrackedGarbage == null) { 136 return; 137 } 138 139 mHandler.sendEmptyMessage(DO_GARBAGE_INSPECTION); 140 } 141 startHeapTracking()142 public void startHeapTracking() { 143 startTrackingProcess( 144 android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis()); 145 mHandler.sendEmptyMessage(DO_HEAP_TRACK); 146 } 147 gcAndCheckGarbage()148 private boolean gcAndCheckGarbage() { 149 if (mTrackedGarbage.countOldGarbage() > GARBAGE_ALLOWANCE) { 150 Runtime.getRuntime().gc(); 151 return true; 152 } 153 return false; 154 } 155 reinspectGarbageAfterGc()156 void reinspectGarbageAfterGc() { 157 int count = mTrackedGarbage.countOldGarbage(); 158 if (count > GARBAGE_ALLOWANCE) { 159 mLeakReporter.dumpLeak(count); 160 } 161 } 162 getMemInfo(int pid)163 public ProcessMemInfo getMemInfo(int pid) { 164 return mData.get(pid); 165 } 166 getTrackedProcesses()167 public int[] getTrackedProcesses() { 168 return mPidsArray; 169 } 170 startTrackingProcess(long pid, String name, long start)171 public void startTrackingProcess(long pid, String name, long start) { 172 synchronized (mPids) { 173 if (mPids.contains(pid)) return; 174 175 mPids.add(pid); 176 updatePidsArrayL(); 177 178 mData.put(pid, new ProcessMemInfo(pid, name, start)); 179 } 180 } 181 updatePidsArrayL()182 private void updatePidsArrayL() { 183 final int N = mPids.size(); 184 mPidsArray = new int[N]; 185 StringBuffer sb = new StringBuffer("Now tracking processes: "); 186 for (int i = 0; i < N; i++) { 187 final int p = mPids.get(i).intValue(); 188 mPidsArray[i] = p; 189 sb.append(p); 190 sb.append(" "); 191 } 192 if (DEBUG) Log.v(TAG, sb.toString()); 193 } 194 update()195 private void update() { 196 synchronized (mPids) { 197 Debug.MemoryInfo[] dinfos = mAm.getProcessMemoryInfo(mPidsArray); 198 for (int i = 0; i < dinfos.length; i++) { 199 Debug.MemoryInfo dinfo = dinfos[i]; 200 if (i > mPids.size()) { 201 if (DEBUG) Log.e(TAG, "update: unknown process info received: " + dinfo); 202 break; 203 } 204 final long pid = mPids.get(i).intValue(); 205 final ProcessMemInfo info = mData.get(pid); 206 info.pss[info.head] = info.currentPss = dinfo.getTotalPss(); 207 info.uss[info.head] = info.currentUss = dinfo.getTotalPrivateDirty(); 208 info.head = (info.head + 1) % info.pss.length; 209 if (info.currentPss > info.max) info.max = info.currentPss; 210 if (info.currentUss > info.max) info.max = info.currentUss; 211 if (info.currentPss == 0) { 212 if (DEBUG) Log.v(TAG, "update: pid " + pid + " has pss=0, it probably died"); 213 mData.remove(pid); 214 } 215 } 216 for (int i = mPids.size() - 1; i >= 0; i--) { 217 final long pid = mPids.get(i).intValue(); 218 if (mData.get(pid) == null) { 219 mPids.remove(i); 220 updatePidsArrayL(); 221 } 222 } 223 } 224 if (mQSTile != null) mQSTile.update(); 225 } 226 setTile(MemoryTile tile)227 private void setTile(MemoryTile tile) { 228 mQSTile = tile; 229 if (tile != null) tile.update(); 230 } 231 formatBytes(long b)232 private static String formatBytes(long b) { 233 String[] SUFFIXES = {"B", "K", "M", "G", "T"}; 234 int i; 235 for (i = 0; i < SUFFIXES.length; i++) { 236 if (b < 1024) break; 237 b /= 1024; 238 } 239 return b + SUFFIXES[i]; 240 } 241 dumpHprofAndGetShareIntent()242 private Intent dumpHprofAndGetShareIntent() { 243 return mDumpTruck.captureHeaps(getTrackedProcesses()).createShareIntent(); 244 } 245 246 @Override dump(@ullable FileDescriptor fd, PrintWriter pw, @Nullable String[] args)247 public void dump(@Nullable FileDescriptor fd, PrintWriter pw, @Nullable String[] args) { 248 pw.println("GarbageMonitor params:"); 249 pw.println(String.format(" mHeapLimit=%d KB", mHeapLimit)); 250 pw.println(String.format(" GARBAGE_INSPECTION_INTERVAL=%d (%.1f mins)", 251 GARBAGE_INSPECTION_INTERVAL, 252 (float) GARBAGE_INSPECTION_INTERVAL / DateUtils.MINUTE_IN_MILLIS)); 253 final float htiMins = HEAP_TRACK_INTERVAL / DateUtils.MINUTE_IN_MILLIS; 254 pw.println(String.format(" HEAP_TRACK_INTERVAL=%d (%.1f mins)", 255 HEAP_TRACK_INTERVAL, 256 htiMins)); 257 pw.println(String.format(" HEAP_TRACK_HISTORY_LEN=%d (%.1f hr total)", 258 HEAP_TRACK_HISTORY_LEN, 259 (float) HEAP_TRACK_HISTORY_LEN * htiMins / 60f)); 260 261 pw.println("GarbageMonitor tracked processes:"); 262 263 for (long pid : mPids) { 264 final ProcessMemInfo pmi = mData.get(pid); 265 if (pmi != null) { 266 pmi.dump(fd, pw, args); 267 } 268 } 269 } 270 271 272 private static class MemoryIconDrawable extends Drawable { 273 long pss, limit; 274 final Drawable baseIcon; 275 final Paint paint = new Paint(); 276 final float dp; 277 MemoryIconDrawable(Context context)278 MemoryIconDrawable(Context context) { 279 baseIcon = context.getDrawable(R.drawable.ic_memory).mutate(); 280 dp = context.getResources().getDisplayMetrics().density; 281 paint.setColor(QSTileImpl.getColorForState(context, STATE_ACTIVE)); 282 } 283 setPss(long pss)284 public void setPss(long pss) { 285 if (pss != this.pss) { 286 this.pss = pss; 287 invalidateSelf(); 288 } 289 } 290 setLimit(long limit)291 public void setLimit(long limit) { 292 if (limit != this.limit) { 293 this.limit = limit; 294 invalidateSelf(); 295 } 296 } 297 298 @Override draw(Canvas canvas)299 public void draw(Canvas canvas) { 300 baseIcon.draw(canvas); 301 302 if (limit > 0 && pss > 0) { 303 float frac = Math.min(1f, (float) pss / limit); 304 305 final Rect bounds = getBounds(); 306 canvas.translate(bounds.left + 8 * dp, bounds.top + 5 * dp); 307 //android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z" 308 canvas.drawRect(0, 14 * dp * (1 - frac), 8 * dp + 1, 14 * dp + 1, paint); 309 } 310 } 311 312 @Override setBounds(int left, int top, int right, int bottom)313 public void setBounds(int left, int top, int right, int bottom) { 314 super.setBounds(left, top, right, bottom); 315 baseIcon.setBounds(left, top, right, bottom); 316 } 317 318 @Override getIntrinsicHeight()319 public int getIntrinsicHeight() { 320 return baseIcon.getIntrinsicHeight(); 321 } 322 323 @Override getIntrinsicWidth()324 public int getIntrinsicWidth() { 325 return baseIcon.getIntrinsicWidth(); 326 } 327 328 @Override setAlpha(int i)329 public void setAlpha(int i) { 330 baseIcon.setAlpha(i); 331 } 332 333 @Override setColorFilter(ColorFilter colorFilter)334 public void setColorFilter(ColorFilter colorFilter) { 335 baseIcon.setColorFilter(colorFilter); 336 paint.setColorFilter(colorFilter); 337 } 338 339 @Override setTint(int tint)340 public void setTint(int tint) { 341 super.setTint(tint); 342 baseIcon.setTint(tint); 343 } 344 345 @Override setTintList(ColorStateList tint)346 public void setTintList(ColorStateList tint) { 347 super.setTintList(tint); 348 baseIcon.setTintList(tint); 349 } 350 351 @Override setTintMode(PorterDuff.Mode tintMode)352 public void setTintMode(PorterDuff.Mode tintMode) { 353 super.setTintMode(tintMode); 354 baseIcon.setTintMode(tintMode); 355 } 356 357 @Override getOpacity()358 public int getOpacity() { 359 return PixelFormat.TRANSLUCENT; 360 } 361 } 362 363 private static class MemoryGraphIcon extends QSTile.Icon { 364 long pss, limit; 365 setPss(long pss)366 public void setPss(long pss) { 367 this.pss = pss; 368 } 369 setHeapLimit(long limit)370 public void setHeapLimit(long limit) { 371 this.limit = limit; 372 } 373 374 @Override getDrawable(Context context)375 public Drawable getDrawable(Context context) { 376 final MemoryIconDrawable drawable = new MemoryIconDrawable(context); 377 drawable.setPss(pss); 378 drawable.setLimit(limit); 379 return drawable; 380 } 381 } 382 383 public static class MemoryTile extends QSTileImpl<QSTile.State> { 384 public static final String TILE_SPEC = "dbg:mem"; 385 386 // Tell QSTileHost.java to toss this into the default tileset? 387 public static final boolean ADD_TO_DEFAULT_ON_DEBUGGABLE_BUILDS = true; 388 389 private final GarbageMonitor gm; 390 private ProcessMemInfo pmi; 391 private boolean dumpInProgress; 392 393 @Inject MemoryTile(QSHost host)394 public MemoryTile(QSHost host) { 395 super(host); 396 gm = SystemUIFactory.getInstance().getRootComponent().createGarbageMonitor(); 397 } 398 399 @Override newTileState()400 public State newTileState() { 401 return new QSTile.State(); 402 } 403 404 @Override getLongClickIntent()405 public Intent getLongClickIntent() { 406 return new Intent(); 407 } 408 409 @Override handleClick()410 protected void handleClick() { 411 if (dumpInProgress) return; 412 413 dumpInProgress = true; 414 refreshState(); 415 new Thread("HeapDumpThread") { 416 @Override 417 public void run() { 418 try { 419 // wait for animations & state changes 420 Thread.sleep(500); 421 } catch (InterruptedException ignored) { } 422 final Intent shareIntent = gm.dumpHprofAndGetShareIntent(); 423 mHandler.post(() -> { 424 dumpInProgress = false; 425 refreshState(); 426 getHost().collapsePanels(); 427 mContext.startActivity(shareIntent); 428 }); 429 } 430 }.start(); 431 } 432 433 @Override getMetricsCategory()434 public int getMetricsCategory() { 435 return VIEW_UNKNOWN; 436 } 437 438 @Override handleSetListening(boolean listening)439 public void handleSetListening(boolean listening) { 440 if (gm != null) gm.setTile(listening ? this : null); 441 442 final ActivityManager am = mContext.getSystemService(ActivityManager.class); 443 if (listening && gm.mHeapLimit > 0) { 444 am.setWatchHeapLimit(1024 * gm.mHeapLimit); // why is this in bytes? 445 } else { 446 am.clearWatchHeapLimit(); 447 } 448 } 449 450 @Override getTileLabel()451 public CharSequence getTileLabel() { 452 return getState().label; 453 } 454 455 @Override handleUpdateState(State state, Object arg)456 protected void handleUpdateState(State state, Object arg) { 457 pmi = gm.getMemInfo(Process.myPid()); 458 final MemoryGraphIcon icon = new MemoryGraphIcon(); 459 icon.setHeapLimit(gm.mHeapLimit); 460 state.state = dumpInProgress ? STATE_UNAVAILABLE : STATE_ACTIVE; 461 state.label = dumpInProgress 462 ? "Dumping..." 463 : mContext.getString(R.string.heap_dump_tile_name); 464 if (pmi != null) { 465 icon.setPss(pmi.currentPss); 466 state.secondaryLabel = 467 String.format( 468 "pss: %s / %s", 469 formatBytes(pmi.currentPss * 1024), 470 formatBytes(gm.mHeapLimit * 1024)); 471 } else { 472 icon.setPss(0); 473 state.secondaryLabel = null; 474 } 475 state.icon = icon; 476 } 477 update()478 public void update() { 479 refreshState(); 480 } 481 getPss()482 public long getPss() { 483 return pmi != null ? pmi.currentPss : 0; 484 } 485 getHeapLimit()486 public long getHeapLimit() { 487 return gm != null ? gm.mHeapLimit : 0; 488 } 489 } 490 491 /** */ 492 public static class ProcessMemInfo implements Dumpable { 493 public long pid; 494 public String name; 495 public long startTime; 496 public long currentPss, currentUss; 497 public long[] pss = new long[HEAP_TRACK_HISTORY_LEN]; 498 public long[] uss = new long[HEAP_TRACK_HISTORY_LEN]; 499 public long max = 1; 500 public int head = 0; 501 ProcessMemInfo(long pid, String name, long start)502 public ProcessMemInfo(long pid, String name, long start) { 503 this.pid = pid; 504 this.name = name; 505 this.startTime = start; 506 } 507 getUptime()508 public long getUptime() { 509 return System.currentTimeMillis() - startTime; 510 } 511 512 @Override dump(@ullable FileDescriptor fd, PrintWriter pw, @Nullable String[] args)513 public void dump(@Nullable FileDescriptor fd, PrintWriter pw, @Nullable String[] args) { 514 pw.print("{ \"pid\": "); 515 pw.print(pid); 516 pw.print(", \"name\": \""); 517 pw.print(name.replace('"', '-')); 518 pw.print("\", \"start\": "); 519 pw.print(startTime); 520 pw.print(", \"pss\": ["); 521 // write pss values starting from the oldest, which is pss[head], wrapping around to 522 // pss[(head-1) % pss.length] 523 for (int i = 0; i < pss.length; i++) { 524 if (i > 0) pw.print(","); 525 pw.print(pss[(head + i) % pss.length]); 526 } 527 pw.print("], \"uss\": ["); 528 for (int i = 0; i < uss.length; i++) { 529 if (i > 0) pw.print(","); 530 pw.print(uss[(head + i) % uss.length]); 531 } 532 pw.println("] }"); 533 } 534 } 535 536 /** */ 537 public static class Service extends SystemUI implements Dumpable { 538 private GarbageMonitor mGarbageMonitor; 539 540 @Override start()541 public void start() { 542 boolean forceEnable = 543 Settings.Secure.getInt( 544 mContext.getContentResolver(), FORCE_ENABLE_LEAK_REPORTING, 0) 545 != 0; 546 mGarbageMonitor = SystemUIFactory.getInstance().getRootComponent() 547 .createGarbageMonitor(); 548 if (LEAK_REPORTING_ENABLED || forceEnable) { 549 mGarbageMonitor.startLeakMonitor(); 550 } 551 if (HEAP_TRACKING_ENABLED || forceEnable) { 552 mGarbageMonitor.startHeapTracking(); 553 } 554 } 555 556 @Override dump(@ullable FileDescriptor fd, PrintWriter pw, @Nullable String[] args)557 public void dump(@Nullable FileDescriptor fd, PrintWriter pw, @Nullable String[] args) { 558 if (mGarbageMonitor != null) mGarbageMonitor.dump(fd, pw, args); 559 } 560 } 561 562 private class BackgroundHeapCheckHandler extends Handler { BackgroundHeapCheckHandler(Looper onLooper)563 BackgroundHeapCheckHandler(Looper onLooper) { 564 super(onLooper); 565 if (Looper.getMainLooper().equals(onLooper)) { 566 throw new RuntimeException( 567 "BackgroundHeapCheckHandler may not run on the ui thread"); 568 } 569 } 570 571 @Override handleMessage(Message m)572 public void handleMessage(Message m) { 573 switch (m.what) { 574 case DO_GARBAGE_INSPECTION: 575 if (gcAndCheckGarbage()) { 576 postDelayed(GarbageMonitor.this::reinspectGarbageAfterGc, 100); 577 } 578 579 removeMessages(DO_GARBAGE_INSPECTION); 580 sendEmptyMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL); 581 break; 582 583 case DO_HEAP_TRACK: 584 update(); 585 removeMessages(DO_HEAP_TRACK); 586 sendEmptyMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL); 587 break; 588 } 589 } 590 } 591 } 592