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