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 com.android.internal.logging.MetricsLogger.VIEW_UNKNOWN; 20 21 import android.app.ActivityManager; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.res.ColorStateList; 25 import android.graphics.Canvas; 26 import android.graphics.ColorFilter; 27 import android.graphics.Paint; 28 import android.graphics.PixelFormat; 29 import android.graphics.PorterDuff; 30 import android.graphics.Rect; 31 import android.graphics.drawable.Drawable; 32 import android.os.Build; 33 import android.os.Debug; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.os.Process; 38 import android.os.SystemProperties; 39 import android.provider.Settings; 40 import android.service.quicksettings.Tile; 41 import android.text.format.DateUtils; 42 import android.util.Log; 43 import android.util.LongSparseArray; 44 45 import com.android.systemui.Dependency; 46 import com.android.systemui.R; 47 import com.android.systemui.SystemUI; 48 import com.android.systemui.plugins.qs.QSTile; 49 import com.android.systemui.qs.QSHost; 50 import com.android.systemui.qs.tileimpl.QSTileImpl; 51 52 import java.util.ArrayList; 53 54 public class GarbageMonitor { 55 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 62 63 private static final String TAG = "GarbageMonitor"; 64 65 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 72 private static final int GARBAGE_ALLOWANCE = 5; 73 74 private final Handler mHandler; 75 private final TrackedGarbage mTrackedGarbage; 76 private final LeakReporter mLeakReporter; 77 private final Context mContext; 78 private final ActivityManager mAm; 79 private MemoryTile mQSTile; 80 private DumpTruck mDumpTruck; 81 82 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 GarbageMonitor( Context context, Looper bgLooper, LeakDetector leakDetector, LeakReporter leakReporter)88 public GarbageMonitor( 89 Context context, 90 Looper bgLooper, 91 LeakDetector leakDetector, 92 LeakReporter leakReporter) { 93 mContext = context.getApplicationContext(); 94 mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 95 96 mHandler = new BackgroundHeapCheckHandler(bgLooper); 97 98 mTrackedGarbage = leakDetector.getTrackedGarbage(); 99 mLeakReporter = leakReporter; 100 101 mDumpTruck = new DumpTruck(mContext); 102 103 if (ENABLE_AM_HEAP_LIMIT) { 104 mHeapLimit = mContext.getResources().getInteger(R.integer.watch_heap_limit); 105 } 106 } 107 startLeakMonitor()108 public void startLeakMonitor() { 109 if (mTrackedGarbage == null) { 110 return; 111 } 112 113 mHandler.sendEmptyMessage(DO_GARBAGE_INSPECTION); 114 } 115 startHeapTracking()116 public void startHeapTracking() { 117 startTrackingProcess( 118 android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis()); 119 mHandler.sendEmptyMessage(DO_HEAP_TRACK); 120 } 121 gcAndCheckGarbage()122 private boolean gcAndCheckGarbage() { 123 if (mTrackedGarbage.countOldGarbage() > GARBAGE_ALLOWANCE) { 124 Runtime.getRuntime().gc(); 125 return true; 126 } 127 return false; 128 } 129 reinspectGarbageAfterGc()130 void reinspectGarbageAfterGc() { 131 int count = mTrackedGarbage.countOldGarbage(); 132 if (count > GARBAGE_ALLOWANCE) { 133 mLeakReporter.dumpLeak(count); 134 } 135 } 136 getMemInfo(int pid)137 public ProcessMemInfo getMemInfo(int pid) { 138 return mData.get(pid); 139 } 140 getTrackedProcesses()141 public int[] getTrackedProcesses() { 142 return mPidsArray; 143 } 144 startTrackingProcess(long pid, String name, long start)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 updatePidsArrayL()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 update()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 setTile(MemoryTile tile)201 private void setTile(MemoryTile tile) { 202 mQSTile = tile; 203 if (tile != null) tile.update(); 204 } 205 formatBytes(long b)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 dumpHprofAndShare()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 MemoryIconDrawable(Context context)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 setPss(long pss)233 public void setPss(long pss) { 234 if (pss != this.pss) { 235 this.pss = pss; 236 invalidateSelf(); 237 } 238 } 239 setLimit(long limit)240 public void setLimit(long limit) { 241 if (limit != this.limit) { 242 this.limit = limit; 243 invalidateSelf(); 244 } 245 } 246 247 @Override draw(Canvas canvas)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 setBounds(int left, int top, int right, int bottom)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 getIntrinsicHeight()268 public int getIntrinsicHeight() { 269 return baseIcon.getIntrinsicHeight(); 270 } 271 272 @Override getIntrinsicWidth()273 public int getIntrinsicWidth() { 274 return baseIcon.getIntrinsicWidth(); 275 } 276 277 @Override setAlpha(int i)278 public void setAlpha(int i) { 279 baseIcon.setAlpha(i); 280 } 281 282 @Override setColorFilter(ColorFilter colorFilter)283 public void setColorFilter(ColorFilter colorFilter) { 284 baseIcon.setColorFilter(colorFilter); 285 paint.setColorFilter(colorFilter); 286 } 287 288 @Override setTint(int tint)289 public void setTint(int tint) { 290 super.setTint(tint); 291 baseIcon.setTint(tint); 292 } 293 294 @Override setTintList(ColorStateList tint)295 public void setTintList(ColorStateList tint) { 296 super.setTintList(tint); 297 baseIcon.setTintList(tint); 298 } 299 300 @Override setTintMode(PorterDuff.Mode tintMode)301 public void setTintMode(PorterDuff.Mode tintMode) { 302 super.setTintMode(tintMode); 303 baseIcon.setTintMode(tintMode); 304 } 305 306 @Override getOpacity()307 public int getOpacity() { 308 return PixelFormat.TRANSLUCENT; 309 } 310 } 311 312 private static class MemoryGraphIcon extends QSTile.Icon { 313 long pss, limit; 314 setPss(long pss)315 public void setPss(long pss) { 316 this.pss = pss; 317 } 318 setHeapLimit(long limit)319 public void setHeapLimit(long limit) { 320 this.limit = limit; 321 } 322 323 @Override getDrawable(Context context)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 MemoryTile(QSHost host)338 public MemoryTile(QSHost host) { 339 super(host); 340 gm = Dependency.get(GarbageMonitor.class); 341 } 342 343 @Override newTileState()344 public State newTileState() { 345 return new QSTile.State(); 346 } 347 348 @Override getLongClickIntent()349 public Intent getLongClickIntent() { 350 return new Intent(); 351 } 352 353 @Override handleClick()354 protected void handleClick() { 355 getHost().collapsePanels(); 356 mHandler.post(gm::dumpHprofAndShare); 357 } 358 359 @Override getMetricsCategory()360 public int getMetricsCategory() { 361 return VIEW_UNKNOWN; 362 } 363 364 @Override handleSetListening(boolean listening)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 getTileLabel()377 public CharSequence getTileLabel() { 378 return getState().label; 379 } 380 381 @Override handleUpdateState(State state, Object arg)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 update()402 public void update() { 403 refreshState(); 404 } 405 getPss()406 public long getPss() { 407 return pmi != null ? pmi.currentPss : 0; 408 } 409 getHeapLimit()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 ProcessMemInfo(long pid, String name, long start)425 public ProcessMemInfo(long pid, String name, long start) { 426 this.pid = pid; 427 this.name = name; 428 this.startTime = start; 429 } 430 getUptime()431 public long getUptime() { 432 return System.currentTimeMillis() - startTime; 433 } 434 } 435 436 public static class Service extends SystemUI { 437 private GarbageMonitor mGarbageMonitor; 438 439 @Override start()440 public void start() { 441 boolean forceEnable = 442 Settings.Secure.getInt( 443 mContext.getContentResolver(), FORCE_ENABLE_LEAK_REPORTING, 0) 444 != 0; 445 mGarbageMonitor = Dependency.get(GarbageMonitor.class); 446 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 { BackgroundHeapCheckHandler(Looper onLooper)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 handleMessage(Message m)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 } 482 } 483 } 484 } 485